Skip to content

Commit

Permalink
fix(feat): use Notification API to notify when the current tab is n…
Browse files Browse the repository at this point in the history
…ot visible.
  • Loading branch information
jibon57 committed Dec 20, 2024
1 parent ef77b9d commit 62b8e43
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 51 deletions.
8 changes: 3 additions & 5 deletions src/components/header/durationView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useTranslation } from 'react-i18next';

import { store } from '../../store';
import { displayInstantNotification } from '../../helpers/utils';

interface IDurationViewProps {
duration: number;
Expand Down Expand Up @@ -59,13 +59,11 @@ const DurationView = ({ duration }: IDurationViewProps) => {
case '30:00':
case '10:00':
case '5:00':
toast(
displayInstantNotification(
t('notifications.room-will-end-in', {
minutes: remaining,
}),
{
type: 'warning',
},
'warning',
);
}
}, [isRecorder, remaining, t]);
Expand Down
6 changes: 6 additions & 0 deletions src/components/main-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ const MainArea = () => {
setAllowChat(false);
dispatch(updateIsActiveChatPanel(false));
}

// ask for notification permission
// we'll not bother if permission was rejected before
if ('Notification' in window && Notification.permission !== 'denied') {
Notification.requestPermission().then();
}
}, [dispatch]);

useEffect(() => {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/hooks/useWatchVisibilityChange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import {
AnalyticsEventType,
DataMsgBodyType,
} from 'plugnmeet-protocol-js';
import { useDispatch } from 'react-redux';

import { getNatsConn } from '../nats';
import { updateIsPNMWindowTabVisible } from '../../store/slices/roomSettingsSlice';

const useWatchVisibilityChange = () => {
const [hidden, setHidden] = useState<boolean>(false);
const conn = getNatsConn();
const dispatch = useDispatch();

useEffect(() => {
const onBlur = () => {
Expand Down Expand Up @@ -82,6 +85,7 @@ const useWatchVisibilityChange = () => {
AnalyticsEventType.USER,
data,
);
dispatch(updateIsPNMWindowTabVisible(!hidden));
//eslint-disable-next-line
}, [hidden]);
};
Expand Down
12 changes: 3 additions & 9 deletions src/helpers/nats/HandleDataMessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { toast } from 'react-toastify';
import { DataChannelMessage, DataMsgBodyType } from 'plugnmeet-protocol-js';

import ConnectNats from './ConnectNats';
Expand All @@ -15,6 +14,7 @@ import {
addExternalMediaPlayerAction,
externalMediaPlayerSeekTo,
} from '../../store/slices/externalMediaPlayer';
import { displayInstantNotification } from '../utils';

export default class HandleDataMessage {
private _that: ConnectNats;
Expand Down Expand Up @@ -47,19 +47,13 @@ export default class HandleDataMessage {
if (payload.fromUserId === this._that.userId || this._that.isRecorder) {
return;
}
toast(payload.message, {
toastId: 'info-status',
type: 'info',
});
displayInstantNotification(payload.message, 'info');
break;
case DataMsgBodyType.ALERT:
if (payload.fromUserId === this._that.userId || this._that.isRecorder) {
return;
}
toast(payload.message, {
toastId: 'alert-status',
type: 'warning',
});
displayInstantNotification(payload.message, 'warning');
break;
case DataMsgBodyType.EXTERNAL_MEDIA_PLAYER_EVENTS:
if (payload.fromUserId === this._that.userId) {
Expand Down
10 changes: 3 additions & 7 deletions src/helpers/nats/HandleParticipants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { toast } from 'react-toastify';
import {
ConnectionQuality,
RemoteTrackPublication,
Expand Down Expand Up @@ -38,6 +37,7 @@ import {
import { updatePlayAudioNotification } from '../../store/slices/roomSettingsSlice';
import { removeOneSpeaker } from '../../store/slices/activeSpeakersSlice';
import { getMediaServerConn } from '../livekit/utils';
import { displayInstantNotification } from '../utils';

export default class HandleParticipants {
private _that: ConnectNats;
Expand Down Expand Up @@ -322,15 +322,11 @@ export default class HandleParticipants {
}
// also play notification
store.dispatch(updatePlayAudioNotification(true));

toast(
displayInstantNotification(
i18n.t('waiting-room.user-waiting', {
name: name,
}),
{
type: 'info',
toastId: 'user-waiting',
},
'info',
);
}
}
Expand Down
24 changes: 11 additions & 13 deletions src/helpers/nats/HandleRoomData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import i18n from '../i18n';
import { addChatMessage } from '../../store/slices/chatMessagesSlice';
import { handleToAddWhiteboardUploadedOfficeNewFile } from '../../components/whiteboard/helpers/utils';
import { WhiteboardFileConversionRes } from '../../store/slices/interfaces/whiteboard';
import { sleep } from '../utils';
import { displayInstantNotification, sleep } from '../utils';

export default class HandleRoomData {
private _room: ICurrentRoom;
Expand Down Expand Up @@ -86,13 +86,15 @@ export default class HandleRoomData {

const isActiveRecording = store.getState().session.isActiveRecording;
if (!isActiveRecording && this._room.metadata?.isRecording) {
toast(i18n.t('room-metadata.session-recording'), {
type: 'info',
});
displayInstantNotification(
i18n.t('room-metadata.session-recording'),
'info',
);
} else if (isActiveRecording && !this._room.metadata?.isRecording) {
toast(i18n.t('room-metadata.session-not-recording'), {
type: 'info',
});
displayInstantNotification(
i18n.t('room-metadata.session-not-recording'),
'info',
);
}
};

Expand All @@ -105,13 +107,9 @@ export default class HandleRoomData {
const isActiveRtmpBroadcasting =
store.getState().session.isActiveRtmpBroadcasting;
if (!isActiveRtmpBroadcasting && this._room.metadata?.isActiveRtmp) {
toast(i18n.t('room-metadata.rtmp-started'), {
type: 'info',
});
displayInstantNotification(i18n.t('room-metadata.rtmp-started'), 'info');
} else if (isActiveRtmpBroadcasting && !this._room.metadata?.isActiveRtmp) {
toast(i18n.t('room-metadata.rtmp-stopped'), {
type: 'info',
});
displayInstantNotification(i18n.t('room-metadata.rtmp-stopped'), 'info');
}
};

Expand Down
29 changes: 12 additions & 17 deletions src/helpers/nats/HandleSystemData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import NewPollMsg from '../../components/extra-pages/newPollMsg';
import { updateReceivedInvitationFor } from '../../store/slices/breakoutRoomSlice';
import { breakoutRoomApi } from '../../store/services/breakoutRoomApi';
import { addChatMessage } from '../../store/slices/chatMessagesSlice';
import { displayInstantNotification } from '../utils';

export default class HandleSystemData {
constructor() {}
Expand All @@ -33,28 +34,19 @@ export default class HandleSystemData {
const nt = fromJsonString(NatsSystemNotificationSchema, data);
switch (nt.type) {
case NatsSystemNotificationTypes.NATS_SYSTEM_NOTIFICATION_INFO:
toast(i18n.t(nt.msg), {
toastId: 'info-status',
type: 'info',
});
displayInstantNotification(i18n.t(nt.msg), 'info');
if (nt.withSound) {
this.playNotification();
}
break;
case NatsSystemNotificationTypes.NATS_SYSTEM_NOTIFICATION_WARNING:
toast(i18n.t(nt.msg), {
toastId: 'info-status',
type: 'warning',
});
displayInstantNotification(i18n.t(nt.msg), 'warning');
if (nt.withSound) {
this.playNotification();
}
break;
case NatsSystemNotificationTypes.NATS_SYSTEM_NOTIFICATION_ERROR:
toast(i18n.t(nt.msg), {
toastId: 'info-status',
type: 'error',
});
displayInstantNotification(i18n.t(nt.msg), 'error');
if (nt.withSound) {
this.playNotification();
}
Expand All @@ -74,22 +66,21 @@ export default class HandleSystemData {
}),
);
} else {
toast(
displayInstantNotification(
i18n.t('speech-services.token-generation-failed', {
error: res.msg,
}),
{
type: 'error',
},
'error',
);
}
};

public handlePoll = (payload: NatsMsgServerToClient) => {
switch (payload.event) {
case NatsMsgServerToClientEvents.POLL_CREATED:
displayInstantNotification(i18n.t('polls.new-poll'), 'info');
toast(<NewPollMsg />, {
toastId: 'info-status',
toastId: 'poll-status',
type: 'info',
autoClose: false,
});
Expand Down Expand Up @@ -126,6 +117,10 @@ export default class HandleSystemData {
switch (payload.event) {
case NatsMsgServerToClientEvents.JOIN_BREAKOUT_ROOM:
if (payload.msg !== '') {
displayInstantNotification(
i18n.t('breakout-room.invitation-msg'),
'info',
);
store.dispatch(updateReceivedInvitationFor(payload.msg));
store.dispatch(breakoutRoomApi.util.invalidateTags(['My_Rooms']));
}
Expand Down
28 changes: 28 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AudioPresets, ScreenSharePresets, VideoPresets } from 'livekit-client';
import { errors } from '@nats-io/nats-core';
import { toast, TypeOptions } from 'react-toastify';

import i18n from './i18n';
import { store } from '../store';
Expand Down Expand Up @@ -230,3 +231,30 @@ export const getWhiteboardDonors = (): IParticipant[] => {

return donors;
};

export const displayInstantNotification = (
message: string,
type: TypeOptions,
) => {
toast(message, {
toastId: type + '-status',
type,
});

const isPNMWindowTabVisible =
store.getState().roomSettings.isPNMWindowTabVisible;
// if not visible, then we can show notification
if (
!isPNMWindowTabVisible &&
'Notification' in window &&
Notification.permission === 'granted'
) {
// we'll see if website has any favicon icon, then we'll use it
const favicon = document.querySelector("link[rel*='icon']");
let icon: string | undefined = undefined;
if (favicon) {
icon = favicon.getAttribute('href') ?? undefined;
}
new Notification(message, { icon });
}
};
1 change: 1 addition & 0 deletions src/store/slices/interfaces/roomSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface IRoomSettings {
visibleHeader: boolean;
visibleFooter: boolean;
azureTokenInfo?: AzureTokenInfo;
isPNMWindowTabVisible: boolean;
}

export interface IMediaDevice {
Expand Down
5 changes: 5 additions & 0 deletions src/store/slices/roomSettingsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const initialState: IRoomSettings = {
columnCameraPosition: ColumnCameraPosition.LEFT,
visibleHeader: true,
visibleFooter: true,
isPNMWindowTabVisible: true,
};

const roomSettingsSlice = createSlice({
Expand Down Expand Up @@ -164,6 +165,9 @@ const roomSettingsSlice = createSlice({
state.azureTokenInfo.token = '';
}
},
updateIsPNMWindowTabVisible: (state, action: PayloadAction<boolean>) => {
state.isPNMWindowTabVisible = action.payload;
},
},
});

Expand Down Expand Up @@ -195,6 +199,7 @@ export const {
updateAzureTokenInfo,
cleanAzureToken,
updateIsNatsServerConnected,
updateIsPNMWindowTabVisible,
} = roomSettingsSlice.actions;

export default roomSettingsSlice.reducer;

0 comments on commit 62b8e43

Please sign in to comment.