Skip to content

Commit 44db3b7

Browse files
committed
client: Read notify search param and post notifications when true
1 parent 57a5c25 commit 44db3b7

2 files changed

Lines changed: 85 additions & 12 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"src/v2/**/*.mjs"
7272
],
7373
"rules": {
74-
"no-console": "error"
74+
"no-console": "warn"
7575
}
7676
}
7777
]

src/v2/components/PotentialFireList.jsx

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
// - Implement adaptive/mobile version.
2525

2626
import React, {useCallback, useEffect, useRef, useState} from 'react'
27+
import Notification from 'react-web-notification'
2728

2829
import Duration from '../modules/Duration.mjs'
2930

@@ -35,6 +36,12 @@ import parseRegion from '../modules/parseRegion.mjs'
3536

3637
import FireList from './FireList.jsx'
3738

39+
const ShouldNotify = {
40+
MIN_INTERVAL_SECONDS: 30,
41+
MAX_RECENT_NOTIFICATIONS: 2,
42+
MAX_RECENT_SECONDS: 5 * 60
43+
}
44+
3845
const TIMESTAMP_LIMIT = 2 * Duration.HOUR
3946

4047
const {error: report} = console
@@ -52,6 +59,8 @@ export default function PotentialFireList(props) {
5259
const [includesAllFires, setIncludesAllFires] = useState(false)
5360
const [indexOfOldFires, setIndexOfOldFires] = useState(-1)
5461
const [region, setRegion] = useState(null)
62+
const [shouldNotify, setShouldNotify] = useState(false)
63+
const [notification, setNotification] = useState(null)
5564

5665
const allFiresRef = useRef([])
5766
const eventSourceRef = useRef()
@@ -69,6 +78,52 @@ export default function PotentialFireList(props) {
6978
setIndexOfOldFires(index)
7079
}, [])
7180

81+
const handleNotification = useCallback(() => {
82+
const {current: allFires} = allFiresRef
83+
const fire = allFires[0]
84+
const {
85+
camInfo: {cameraName, cameraDir = 'UNKNOWN'},
86+
isRealTime, notified = false, timestamp
87+
} = fire
88+
89+
if (notified || !isRealTime) {
90+
return report('Failed precondition: `fire` should be real-time and shouldn’t be notified')
91+
}
92+
93+
// -------------------------------------------------------------------------
94+
// Prevent notifications from appearing too frequently.
95+
96+
const notifiedFires = allFires.filter((x) => x.isRealTime && x.notified)
97+
const mru = notifiedFires.length > 0 ? notifiedFires[0].timestamp : 0
98+
99+
// At least MIN_INTERVAL_SECONDS between notifications.
100+
if (timestamp - mru > ShouldNotify.MIN_INTERVAL_SECONDS) {
101+
return
102+
}
103+
104+
const timestampLimit = timestamp - ShouldNotify.MAX_RECENT_SECONDS
105+
const recentlyNotifiedFires = notifiedFires.filter((x) => x.timestamp > timestampLimit)
106+
107+
// At most MAX_RECENT_NOTIFICATIONS every MAX_RECENT_SECONDS.
108+
if (recentlyNotifiedFires.length >= ShouldNotify.MAX_RECENT_NOTIFICATIONS) {
109+
return
110+
}
111+
112+
// -------------------------------------------------------------------------
113+
114+
fire.notified = true
115+
116+
setNotification({
117+
title: 'Potential fire',
118+
options: {
119+
body: `Camera ${cameraName} facing ${cameraDir}`,
120+
icon: '/wildfirecheck/checkfire192.png',
121+
lang: 'en',
122+
tag: `${timestamp}`
123+
}
124+
})
125+
}, [])
126+
72127
const handleToggleAllFires = useCallback(() => {
73128
const shouldIncludeAllFires = !includesAllFires
74129
setIncludesAllFires(shouldIncludeAllFires)
@@ -77,7 +132,7 @@ export default function PotentialFireList(props) {
77132

78133
const handlePotentialFire = useCallback((event) => {
79134
const fire = JSON.parse(event.data)
80-
const {croppedUrl, polygon, version} = fire
135+
const {cameraID, croppedUrl, polygon, timestamp, version} = fire
81136

82137
if (region != null && !isPolygonWithinRegion(polygon, region)) {
83138
return false
@@ -113,14 +168,28 @@ export default function PotentialFireList(props) {
113168
allFires.unshift(fire)
114169
allFires.sort((a, b) => b.sortId - a.sortId)
115170

171+
const first = allFires[0]
172+
if (shouldNotify && first != null && first.isRealTime) {
173+
if (first.timestamp === timestamp && first.cameraID === cameraID) {
174+
handleNotification()
175+
}
176+
}
177+
116178
updateFires(includesAllFires)
117179
}
118-
}, [includesAllFires, region, updateFires])
180+
}, [handleNotification, includesAllFires, region, shouldNotify, updateFires])
119181

120182
useEffect(() => {
121183
const searchParams = new URLSearchParams(window.location.search)
184+
const notifyParam = searchParams.get('notify')
122185
const regionParam = searchParams.get('latLong')
123186

187+
if (notifyParam != null) {
188+
if (/true|false/.test(notifyParam)) {
189+
setShouldNotify(notifyParam === 'true')
190+
}
191+
}
192+
124193
if (regionParam != null) {
125194
try {
126195
setRegion(parseRegion(regionParam))
@@ -154,13 +223,17 @@ export default function PotentialFireList(props) {
154223
return tidy
155224
}, [handlePotentialFire])
156225

157-
return <FireList
158-
fires={fires}
159-
firesByKey={firesByKeyRef.current}
160-
indexOfOldFires={includesAllFires ? indexOfOldFires : -1}
161-
nOldFires={indexOfOldFires > -1 ? allFiresRef.current.length - indexOfOldFires : 0}
162-
onToggleAllFires={handleToggleAllFires}
163-
region={region}
164-
updateFires={updateFires}
165-
{...props}/>
226+
return 0,
227+
<>
228+
{ shouldNotify && notification && <Notification {...notification}/> }
229+
<FireList
230+
fires={fires}
231+
firesByKey={firesByKeyRef.current}
232+
indexOfOldFires={includesAllFires ? indexOfOldFires : -1}
233+
nOldFires={indexOfOldFires > -1 ? allFiresRef.current.length - indexOfOldFires : 0}
234+
onToggleAllFires={handleToggleAllFires}
235+
region={region}
236+
updateFires={updateFires}
237+
{...props}/>
238+
</>
166239
}

0 commit comments

Comments
 (0)