@@ -20,6 +20,9 @@ import {
2020 BeaconEvent ,
2121 MatrixEvent ,
2222 Room ,
23+ RoomMember ,
24+ RoomState ,
25+ RoomStateEvent ,
2326} from "matrix-js-sdk/src/matrix" ;
2427import {
2528 BeaconInfoState , makeBeaconContent , makeBeaconInfoContent ,
@@ -90,6 +93,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
9093 protected async onNotReady ( ) {
9194 this . matrixClient . removeListener ( BeaconEvent . LivenessChange , this . onBeaconLiveness ) ;
9295 this . matrixClient . removeListener ( BeaconEvent . New , this . onNewBeacon ) ;
96+ this . matrixClient . removeListener ( RoomStateEvent . Members , this . onRoomStateMembers ) ;
9397
9498 this . beacons . forEach ( beacon => beacon . destroy ( ) ) ;
9599
@@ -102,6 +106,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
102106 protected async onReady ( ) : Promise < void > {
103107 this . matrixClient . on ( BeaconEvent . LivenessChange , this . onBeaconLiveness ) ;
104108 this . matrixClient . on ( BeaconEvent . New , this . onNewBeacon ) ;
109+ this . matrixClient . on ( RoomStateEvent . Members , this . onRoomStateMembers ) ;
105110
106111 this . initialiseBeaconState ( ) ;
107112 }
@@ -136,6 +141,10 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
136141 return await this . updateBeaconEvent ( beacon , { live : false } ) ;
137142 } ;
138143
144+ /**
145+ * Listeners
146+ */
147+
139148 private onNewBeacon = ( _event : MatrixEvent , beacon : Beacon ) : void => {
140149 if ( ! isOwnBeacon ( beacon , this . matrixClient . getUserId ( ) ) ) {
141150 return ;
@@ -160,6 +169,33 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
160169 this . emit ( OwnBeaconStoreEvent . LivenessChange , this . getLiveBeaconIds ( ) ) ;
161170 } ;
162171
172+ /**
173+ * Check for changes in membership in rooms with beacons
174+ * and stop monitoring beacons in rooms user is no longer member of
175+ */
176+ private onRoomStateMembers = ( _event : MatrixEvent , roomState : RoomState , member : RoomMember ) : void => {
177+ // no beacons for this room, ignore
178+ if (
179+ ! this . beaconsByRoomId . has ( roomState . roomId ) ||
180+ member . userId !== this . matrixClient . getUserId ( )
181+ ) {
182+ return ;
183+ }
184+
185+ // TODO check powerlevels here
186+ // in PSF-797
187+
188+ // stop watching beacons in rooms where user is no longer a member
189+ if ( member . membership === 'leave' || member . membership === 'ban' ) {
190+ this . beaconsByRoomId . get ( roomState . roomId ) . forEach ( this . removeBeacon ) ;
191+ this . beaconsByRoomId . delete ( roomState . roomId ) ;
192+ }
193+ } ;
194+
195+ /**
196+ * State management
197+ */
198+
163199 private initialiseBeaconState = ( ) => {
164200 const userId = this . matrixClient . getUserId ( ) ;
165201 const visibleRooms = this . matrixClient . getVisibleRooms ( ) ;
@@ -187,6 +223,21 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
187223 beacon . monitorLiveness ( ) ;
188224 } ;
189225
226+ /**
227+ * Remove listeners for a given beacon
228+ * remove from state
229+ * and update liveness if changed
230+ */
231+ private removeBeacon = ( beaconId : string ) : void => {
232+ if ( ! this . beacons . has ( beaconId ) ) {
233+ return ;
234+ }
235+ this . beacons . get ( beaconId ) . destroy ( ) ;
236+ this . beacons . delete ( beaconId ) ;
237+
238+ this . checkLiveness ( ) ;
239+ } ;
240+
190241 private checkLiveness = ( ) : void => {
191242 const prevLiveBeaconIds = this . getLiveBeaconIds ( ) ;
192243 this . liveBeaconIds = [ ...this . beacons . values ( ) ]
@@ -218,20 +269,9 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
218269 }
219270 } ;
220271
221- private updateBeaconEvent = async ( beacon : Beacon , update : Partial < BeaconInfoState > ) : Promise < void > => {
222- const { description, timeout, timestamp, live, assetType } = {
223- ...beacon . beaconInfo ,
224- ...update ,
225- } ;
226-
227- const updateContent = makeBeaconInfoContent ( timeout ,
228- live ,
229- description ,
230- assetType ,
231- timestamp ) ;
232-
233- await this . matrixClient . unstable_setLiveBeacon ( beacon . roomId , beacon . beaconInfoEventType , updateContent ) ;
234- } ;
272+ /**
273+ * Geolocation
274+ */
235275
236276 private togglePollingLocation = ( ) => {
237277 if ( ! ! this . liveBeaconIds . length ) {
@@ -270,17 +310,6 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
270310 this . emit ( OwnBeaconStoreEvent . MonitoringLivePosition ) ;
271311 } ;
272312
273- private onWatchedPosition = ( position : GeolocationPosition ) => {
274- const timedGeoPosition = mapGeolocationPositionToTimedGeo ( position ) ;
275-
276- // if this is our first position, publish immediateley
277- if ( ! this . lastPublishedPositionTimestamp ) {
278- this . publishLocationToBeacons ( timedGeoPosition ) ;
279- } else {
280- this . debouncedPublishLocationToBeacons ( timedGeoPosition ) ;
281- }
282- } ;
283-
284313 private stopPollingLocation = ( ) => {
285314 clearInterval ( this . locationInterval ) ;
286315 this . locationInterval = undefined ;
@@ -295,26 +324,34 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
295324 this . emit ( OwnBeaconStoreEvent . MonitoringLivePosition ) ;
296325 } ;
297326
298- /**
299- * Sends m.location events to all live beacons
300- * Sets last published beacon
301- */
302- private publishLocationToBeacons = async ( position : TimedGeoUri ) => {
303- this . lastPublishedPositionTimestamp = Date . now ( ) ;
304- // TODO handle failure in individual beacon without rejecting rest
305- await Promise . all ( this . liveBeaconIds . map ( beaconId =>
306- this . sendLocationToBeacon ( this . beacons . get ( beaconId ) , position ) ) ,
307- ) ;
327+ private onWatchedPosition = ( position : GeolocationPosition ) => {
328+ const timedGeoPosition = mapGeolocationPositionToTimedGeo ( position ) ;
329+
330+ // if this is our first position, publish immediateley
331+ if ( ! this . lastPublishedPositionTimestamp ) {
332+ this . publishLocationToBeacons ( timedGeoPosition ) ;
333+ } else {
334+ this . debouncedPublishLocationToBeacons ( timedGeoPosition ) ;
335+ }
308336 } ;
309337
310- private debouncedPublishLocationToBeacons = debounce ( this . publishLocationToBeacons , MOVING_UPDATE_INTERVAL ) ;
338+ private onGeolocationError = async ( error : GeolocationError ) : Promise < void > => {
339+ this . geolocationError = error ;
340+ logger . error ( 'Geolocation failed' , this . geolocationError ) ;
311341
312- /**
313- * Sends m.location event to referencing given beacon
314- */
315- private sendLocationToBeacon = async ( beacon : Beacon , { geoUri, timestamp } : TimedGeoUri ) => {
316- const content = makeBeaconContent ( geoUri , timestamp , beacon . beaconInfoId ) ;
317- await this . matrixClient . sendEvent ( beacon . roomId , M_BEACON . name , content ) ;
342+ // other errors are considered non-fatal
343+ // and self recovering
344+ if ( ! [
345+ GeolocationError . Unavailable ,
346+ GeolocationError . PermissionDenied ,
347+ ] . includes ( error ) ) {
348+ return ;
349+ }
350+
351+ this . stopPollingLocation ( ) ;
352+ // kill live beacons when location permissions are revoked
353+ // TODO may need adjustment when PSF-797 is done
354+ await Promise . all ( this . liveBeaconIds . map ( this . stopBeacon ) ) ;
318355 } ;
319356
320357 /**
@@ -332,22 +369,44 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
332369 }
333370 } ;
334371
335- private onGeolocationError = async ( error : GeolocationError ) : Promise < void > => {
336- this . geolocationError = error ;
337- logger . error ( 'Geolocation failed' , this . geolocationError ) ;
372+ /**
373+ * MatrixClient api
374+ */
338375
339- // other errors are considered non-fatal
340- // and self recovering
341- if ( ! [
342- GeolocationError . Unavailable ,
343- GeolocationError . PermissionDenied ,
344- ] . includes ( error ) ) {
345- return ;
346- }
376+ private updateBeaconEvent = async ( beacon : Beacon , update : Partial < BeaconInfoState > ) : Promise < void > => {
377+ const { description, timeout, timestamp, live, assetType } = {
378+ ...beacon . beaconInfo ,
379+ ...update ,
380+ } ;
347381
348- this . stopPollingLocation ( ) ;
349- // kill live beacons when location permissions are revoked
350- // TODO may need adjustment when PSF-797 is done
351- await Promise . all ( this . liveBeaconIds . map ( this . stopBeacon ) ) ;
382+ const updateContent = makeBeaconInfoContent ( timeout ,
383+ live ,
384+ description ,
385+ assetType ,
386+ timestamp ) ;
387+
388+ await this . matrixClient . unstable_setLiveBeacon ( beacon . roomId , beacon . beaconInfoEventType , updateContent ) ;
389+ } ;
390+
391+ /**
392+ * Sends m.location events to all live beacons
393+ * Sets last published beacon
394+ */
395+ private publishLocationToBeacons = async ( position : TimedGeoUri ) => {
396+ this . lastPublishedPositionTimestamp = Date . now ( ) ;
397+ // TODO handle failure in individual beacon without rejecting rest
398+ await Promise . all ( this . liveBeaconIds . map ( beaconId =>
399+ this . sendLocationToBeacon ( this . beacons . get ( beaconId ) , position ) ) ,
400+ ) ;
401+ } ;
402+
403+ private debouncedPublishLocationToBeacons = debounce ( this . publishLocationToBeacons , MOVING_UPDATE_INTERVAL ) ;
404+
405+ /**
406+ * Sends m.location event to referencing given beacon
407+ */
408+ private sendLocationToBeacon = async ( beacon : Beacon , { geoUri, timestamp } : TimedGeoUri ) => {
409+ const content = makeBeaconContent ( geoUri , timestamp , beacon . beaconInfoId ) ;
410+ await this . matrixClient . sendEvent ( beacon . roomId , M_BEACON . name , content ) ;
352411 } ;
353412}
0 commit comments