@djangocfg/monitor 2.1.238 → 2.1.240

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/dist/index.d.cts CHANGED
@@ -85,6 +85,10 @@ interface MonitorConfig {
85
85
  captureJsErrors?: boolean;
86
86
  /** Log debug info to console. Default: false */
87
87
  debug?: boolean;
88
+ /** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
89
+ dedupeTtl?: number;
90
+ /** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
91
+ dedupeStoreTtl?: number;
88
92
  }
89
93
  interface ServerMonitorConfig {
90
94
  /** Base URL for the django-cfg backend (absolute URL required on server) */
package/dist/index.d.ts CHANGED
@@ -85,6 +85,10 @@ interface MonitorConfig {
85
85
  captureJsErrors?: boolean;
86
86
  /** Log debug info to console. Default: false */
87
87
  debug?: boolean;
88
+ /** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
89
+ dedupeTtl?: number;
90
+ /** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
91
+ dedupeStoreTtl?: number;
88
92
  }
89
93
  interface ServerMonitorConfig {
90
94
  /** Base URL for the django-cfg backend (absolute URL required on server) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/monitor",
3
- "version": "2.1.238",
3
+ "version": "2.1.240",
4
4
  "description": "Browser error and event monitoring SDK for django-cfg backends. Captures JS errors, network failures, console logs, and performance metrics.",
5
5
  "keywords": [
6
6
  "django",
@@ -83,7 +83,7 @@
83
83
  }
84
84
  },
85
85
  "devDependencies": {
86
- "@djangocfg/typescript-config": "^2.1.238",
86
+ "@djangocfg/typescript-config": "^2.1.240",
87
87
  "p-retry": "^6.2.0",
88
88
  "@types/node": "^24.7.2",
89
89
  "@types/react": "^19.1.0",
@@ -1,7 +1,7 @@
1
1
  import { computeFingerprint } from './fingerprint'
2
2
  import { getSessionId } from './session'
3
3
  import { monitorStore } from '../store'
4
- import { MONITOR_INGEST_PATTERN } from '../constants'
4
+ import { MONITOR_INGEST_PATTERN, DEFAULT_DEDUPE_TTL } from '../constants'
5
5
  import { EventType, EventLevel } from '../../_api'
6
6
  import type { FrontendEventIngestRequestEventType, FrontendEventIngestRequestLevel } from '../../_api/generated/cfg_monitor/enums'
7
7
 
@@ -18,14 +18,14 @@ const typeMap: Record<ConsoleLevel, FrontendEventIngestRequestEventType> = {
18
18
 
19
19
  const ARG_MAX = 500
20
20
 
21
- // Dedup: skip re-sending the same console message within TTL
22
- const CONSOLE_DEDUP_TTL = 5 * 60 * 1000 // 5 minutes
21
+ // Dedup: skip re-sending the same console message within TTL (read from config)
23
22
  const recentConsoleFingerprints = new Map<string, number>()
24
23
 
25
24
  function isRecentConsole(fingerprint: string): boolean {
25
+ const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
26
26
  const now = Date.now()
27
27
  const last = recentConsoleFingerprints.get(fingerprint)
28
- if (last !== undefined && now - last < CONSOLE_DEDUP_TTL) return true
28
+ if (last !== undefined && now - last < ttl) return true
29
29
  recentConsoleFingerprints.set(fingerprint, now)
30
30
  if (recentConsoleFingerprints.size > 100) {
31
31
  const oldest = [...recentConsoleFingerprints.entries()].sort((a, b) => a[1] - b[1])[0]
@@ -1,7 +1,7 @@
1
1
  import { computeFingerprint } from './fingerprint'
2
2
  import { getSessionId } from './session'
3
3
  import { monitorStore } from '../store'
4
- import { MONITOR_INGEST_PATTERN } from '../constants'
4
+ import { MONITOR_INGEST_PATTERN, DEFAULT_DEDUPE_TTL } from '../constants'
5
5
  import { EventType, EventLevel } from '../../_api'
6
6
 
7
7
  const MSG_MAX = 2000
@@ -29,17 +29,16 @@ function isHydrationNoise(msg: string): boolean {
29
29
  return HYDRATION_NOISE.some((p) => p.test(msg))
30
30
  }
31
31
 
32
- // Client-side dedup: skip re-sending the same fingerprint within CLIENT_DEDUP_TTL ms.
32
+ // Client-side dedup: skip re-sending the same fingerprint within dedupeTtl.
33
33
  // Protects against crash-loop pages flooding the ingest endpoint.
34
- const CLIENT_DEDUP_TTL = 5 * 60 * 1000 // 5 minutes
35
34
  const recentFingerprints = new Map<string, number>()
36
35
 
37
36
  function isRecentlySent(fingerprint: string): boolean {
37
+ const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
38
38
  const now = Date.now()
39
39
  const last = recentFingerprints.get(fingerprint)
40
- if (last !== undefined && now - last < CLIENT_DEDUP_TTL) return true
40
+ if (last !== undefined && now - last < ttl) return true
41
41
  recentFingerprints.set(fingerprint, now)
42
- // Cap map size to avoid memory growth — evict oldest entry when over limit
43
42
  if (recentFingerprints.size > 100) {
44
43
  const oldest = [...recentFingerprints.entries()].sort((a, b) => a[1] - b[1])[0]
45
44
  recentFingerprints.delete(oldest[0])
@@ -1,19 +1,20 @@
1
1
  import { getSessionId } from './session'
2
2
  import { monitorStore } from '../store'
3
+ import { DEFAULT_DEDUPE_TTL } from '../constants'
3
4
  import { EventType, EventLevel } from '../../_api'
4
5
 
5
- // Dedup: skip re-sending the same network error within TTL
6
- const NETWORK_DEDUP_TTL = 5_000 // 5 seconds
6
+ // Dedup: skip re-sending the same network error within TTL (read from config)
7
7
  const recentNetworkErrors = new Map<string, number>()
8
8
 
9
9
  function isRecentNetwork(key: string): boolean {
10
+ const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
10
11
  const now = Date.now()
11
12
  const last = recentNetworkErrors.get(key)
12
- if (last !== undefined && now - last < NETWORK_DEDUP_TTL) return true
13
+ if (last !== undefined && now - last < ttl) return true
13
14
  recentNetworkErrors.set(key, now)
14
15
  if (recentNetworkErrors.size > 100) {
15
16
  for (const [k, ts] of recentNetworkErrors) {
16
- if (now - ts > NETWORK_DEDUP_TTL) recentNetworkErrors.delete(k)
17
+ if (now - ts > ttl) recentNetworkErrors.delete(k)
17
18
  }
18
19
  }
19
20
  return false
@@ -1,19 +1,20 @@
1
1
  import { getSessionId } from './session'
2
2
  import { monitorStore } from '../store'
3
+ import { DEFAULT_DEDUPE_TTL } from '../constants'
3
4
  import { EventType, EventLevel } from '../../_api'
4
5
 
5
- // Dedup: skip re-sending the same validation error within TTL
6
- const VALIDATION_DEDUP_TTL = 10_000 // 10 seconds
6
+ // Dedup: skip re-sending the same validation error within TTL (read from config)
7
7
  const recentValidations = new Map<string, number>()
8
8
 
9
9
  function isRecentValidation(key: string): boolean {
10
+ const ttl = monitorStore.getState().config.dedupeTtl ?? DEFAULT_DEDUPE_TTL
10
11
  const now = Date.now()
11
12
  const last = recentValidations.get(key)
12
- if (last !== undefined && now - last < VALIDATION_DEDUP_TTL) return true
13
+ if (last !== undefined && now - last < ttl) return true
13
14
  recentValidations.set(key, now)
14
15
  if (recentValidations.size > 50) {
15
16
  for (const [k, ts] of recentValidations) {
16
- if (now - ts > VALIDATION_DEDUP_TTL) recentValidations.delete(k)
17
+ if (now - ts > ttl) recentValidations.delete(k)
17
18
  }
18
19
  }
19
20
  return false
@@ -5,3 +5,9 @@
5
5
 
6
6
  /** Matches any request to the monitor ingest endpoint */
7
7
  export const MONITOR_INGEST_PATTERN = /cfg\/monitor\/ingest/
8
+
9
+ /** Default deduplication TTL for per-capture-source filtering (ms) */
10
+ export const DEFAULT_DEDUPE_TTL = 5_000
11
+
12
+ /** Default deduplication TTL for global store-level filtering (ms) */
13
+ export const DEFAULT_DEDUPE_STORE_TTL = 30_000
@@ -2,6 +2,7 @@ import { createStore } from 'zustand/vanilla'
2
2
  import type { MonitorEvent, MonitorConfig } from '../../types'
3
3
  import { sendBatch } from '../transport/ingest'
4
4
  import { MONITOR_VERSION } from '../utils/env'
5
+ import { DEFAULT_DEDUPE_STORE_TTL } from '../constants'
5
6
 
6
7
  // Circuit breaker: pause flushing after N consecutive transport failures
7
8
  const CIRCUIT_BREAKER_THRESHOLD = 3
@@ -10,25 +11,22 @@ const CIRCUIT_BREAKER_COOLDOWN_MS = 60_000 // 1 minute
10
11
  // Global deduplication: skip pushing identical events within this window.
11
12
  // Protects against multiple capture sources (js-errors, console, ErrorTrackingProvider)
12
13
  // all firing for the same underlying error.
13
- const STORE_DEDUP_TTL = 5_000 // 5 seconds
14
14
  const STORE_DEDUP_MAX = 200
15
15
  const _recentPushKeys = new Map<string, number>()
16
16
 
17
17
  function _pushDedupeKey(event: MonitorEvent): string {
18
- // Build a key from event_type + level + message (first 100 chars) + url
19
18
  const msg = (event.message ?? '').slice(0, 100)
20
19
  return `${event.event_type}:${event.level}:${msg}:${event.http_url ?? event.url ?? ''}`
21
20
  }
22
21
 
23
- function _isRecentPush(key: string): boolean {
22
+ function _isRecentPush(key: string, ttl: number): boolean {
24
23
  const now = Date.now()
25
24
  const last = _recentPushKeys.get(key)
26
- if (last !== undefined && now - last < STORE_DEDUP_TTL) return true
25
+ if (last !== undefined && now - last < ttl) return true
27
26
  _recentPushKeys.set(key, now)
28
- // Evict old entries when map grows too large
29
27
  if (_recentPushKeys.size > STORE_DEDUP_MAX) {
30
28
  for (const [k, ts] of _recentPushKeys) {
31
- if (now - ts > STORE_DEDUP_TTL) _recentPushKeys.delete(k)
29
+ if (now - ts > ttl) _recentPushKeys.delete(k)
32
30
  }
33
31
  }
34
32
  return false
@@ -54,10 +52,10 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
54
52
 
55
53
  push(event) {
56
54
  // Global dedup: skip if an identical event was pushed recently
57
- const dedupeKey = _pushDedupeKey(event)
58
- if (_isRecentPush(dedupeKey)) return
59
-
60
55
  const { config, buffer } = get()
56
+ const storeTtl = config.dedupeStoreTtl ?? DEFAULT_DEDUPE_STORE_TTL
57
+ const dedupeKey = _pushDedupeKey(event)
58
+ if (_isRecentPush(dedupeKey, storeTtl)) return
61
59
  const maxSize = config.maxBufferSize ?? 20
62
60
  const sanitized: MonitorEvent = {
63
61
  build_id: event.build_id ?? config.buildId ?? `sdk:${MONITOR_VERSION}`,
@@ -92,8 +90,8 @@ export const monitorStore = createStore<MonitorState>((set, get) => ({
92
90
  // Circuit breaker: skip sending while paused
93
91
  if (Date.now() < _pausedUntil) return
94
92
 
95
- const batch = buffer.slice(0, 50)
96
- set({ buffer: buffer.slice(50) })
93
+ const batch = buffer.slice(0, 25)
94
+ set({ buffer: buffer.slice(25) })
97
95
 
98
96
  sendBatch({ events: batch }, useBeacon).then(
99
97
  () => {
@@ -21,6 +21,10 @@ export interface MonitorConfig {
21
21
  captureJsErrors?: boolean
22
22
  /** Log debug info to console. Default: false */
23
23
  debug?: boolean
24
+ /** Deduplication TTL in ms for per-capture-source filtering. Default: 5000 */
25
+ dedupeTtl?: number
26
+ /** Deduplication TTL in ms for the global store-level filter (cross-source). Default: 30000 */
27
+ dedupeStoreTtl?: number
24
28
  }
25
29
 
26
30
  export interface ServerMonitorConfig {