Skip to content

Commit

Permalink
Add outage notifications system (n-air-app#501)
Browse files Browse the repository at this point in the history
* add outage notifications system

* fix caching, mark as read after clicking notification

* clarify comment

* make methods private
  • Loading branch information
avacreeth authored Jun 13, 2018
1 parent f8b701a commit 6c878b2
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 4 deletions.
5 changes: 3 additions & 2 deletions app/components/NotificationsArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@
.notification {
height: 30px;
max-width: 100%;
line-height: 30px;
padding-left: 10px;
padding-right: 10px;
border-radius: 3px;
white-space: nowrap;
overflow: hidden;
margin-left: 10px;
text-overflow: ellipsis;
position: absolute;
left: 20px;
animation: notify-appears 0.3s;
Expand Down Expand Up @@ -97,6 +97,7 @@
.notifications__counter {
cursor: pointer;
white-space: nowrap;
margin-right: 10px;
&:before {
content: '|';
Expand Down
3 changes: 2 additions & 1 deletion app/components/NotificationsArea.vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default class NotificationsArea extends Vue {
this.sizeCheckIntervalId = window.setInterval(() => {
if (!this.$refs.notificationsContainer) return;

if (this.$refs.notificationsContainer.offsetWidth < 300) {
if (this.$refs.notificationsContainer.offsetWidth < 150) {
this.showExtendedNotifications = false;
} else {
this.showExtendedNotifications = true;
Expand Down Expand Up @@ -142,6 +142,7 @@ export default class NotificationsArea extends Vue {
const notify = this.notifications.find(notify => notify.id === id);
if (notify.outdated) return;
this.notificationsService.applyAction(id);
this.notificationsService.markAsRead(id);
notify.outdated = true;
}

Expand Down
4 changes: 3 additions & 1 deletion app/services-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { WebsocketService } from 'services/websocket';
import { ProjectorService } from 'services/projector';
import { I18nService } from 'services/i18n';
import { MediaBackupService } from 'services/media-backup';
import { OutageNotificationsService } from 'services/outage-notifications';

const { ipcRenderer } = electron;

Expand Down Expand Up @@ -140,7 +141,8 @@ export class ServicesManager extends Service {
I18nService,
TransitionsService,
MediaBackupService,
WebsocketService
WebsocketService,
OutageNotificationsService
};

private instances: Dictionary<Service> = {};
Expand Down
3 changes: 3 additions & 0 deletions app/services/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { FileManagerService } from 'services/file-manager';
import { PatchNotesService } from 'services/patch-notes';
import { ProtocolLinksService } from 'services/protocol-links';
import { WindowsService } from 'services/windows';
import { OutageNotificationsService } from 'services/outage-notifications';

interface IAppState {
loading: boolean;
Expand All @@ -39,6 +40,7 @@ export class AppService extends StatefulService<IAppState> {
@Inject() streamInfoService: StreamInfoService;
@Inject() patchNotesService: PatchNotesService;
@Inject() windowsService: WindowsService;
@Inject() outageNotificationsService: OutageNotificationsService;

static initialState: IAppState = {
loading: true,
Expand Down Expand Up @@ -87,6 +89,7 @@ export class AppService extends StatefulService<IAppState> {
this.tcpServerService.listen();

this.patchNotesService.showPatchNotesIfRequired(onboarded);
this.outageNotificationsService;

this.FINISH_LOADING();
});
Expand Down
1 change: 1 addition & 0 deletions app/services/notifications/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export class NotificationsService extends PersistentStatefulService<
markAsRead(id: number) {
const notify = this.getNotification(id);
if (!notify) return;
this.MARK_AS_READ(id);
this.notificationRead.next([id]);
}

Expand Down
124 changes: 124 additions & 0 deletions app/services/outage-notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Service } from 'services/service';
import { NotificationsService, ENotificationType } from 'services/notifications';
import { Inject } from 'util/injector';
import { JsonrpcService, IJsonRpcRequest } from 'services/jsonrpc';
import electron from 'electron';

interface IOutageNotification {
/**
* Uniquely identifies this message
*/
id: string;

/**
* The body of the message
*/
message: string;

/**
* If set, clicking the notification will
* open a browser and navigate to this URL.
*/
url?: string;

/**
* This message should be ignored if disabled is true
*/
disabled: boolean;
}

// Configuration
const S3_BUCKET = 'streamlabs-obs';
const S3_KEY = 'outage-notification.json';
const POLLING_INTERVAL = 5 * 60 * 1000;

export class OutageNotificationsService extends Service {
@Inject() notificationsService: NotificationsService;
@Inject() jsonrpcService: JsonrpcService;

currentMessageId: string = null;
currentNotificationId: number = null;

init() {
this.checkForNotification();
setInterval(() => this.checkForNotification(), POLLING_INTERVAL);
}

private pushNotification(message: string, url?: string) {
let action: IJsonRpcRequest;

action = this.jsonrpcService.createRequest(
Service.getResourceId(this.notificationsService),
'showNotifications'
);

if (url) {
action = this.jsonrpcService.createRequest(
Service.getResourceId(this),
'openBrowserWindow',
url
);
}

return this.notificationsService.push({
message,
type: ENotificationType.WARNING,
lifeTime: -1,
action
});
}

private openBrowserWindow(url: string) {
electron.remote.shell.openExternal(url);
}

private async checkForNotification() {
const msg = await this.fetchMessageJson();

// There are no urgent messages to display to the user
if (!msg || msg.disabled) {
this.clearNotification();
return;
}

// The current message is still in effect
if (this.currentMessageId === msg.id) return;

// There is a new message to display to the user
const notification = this.pushNotification(msg.message, msg.url);
this.currentMessageId = msg.id;
this.currentNotificationId = notification.id;
}

private clearNotification() {
if (this.currentMessageId) this.currentMessageId = null;
if (this.currentNotificationId) {
this.notificationsService.markAsRead(this.currentNotificationId);
this.currentNotificationId = null;
}
}

private async fetchMessageJson(): Promise<IOutageNotification> {
const req = new Request(this.messageUrl);
const headers = new Headers();
headers.append('Pragma', 'no-cache');
headers.append('Cache-Control', 'no-cache');

try {
const response = await fetch(req, { headers });

if (response.ok) {
return await response.json();
}

return;
} catch (e) {
return;
}
}

private get messageUrl() {
return `https://s3-us-west-2.amazonaws.com/${S3_BUCKET}/${S3_KEY}`;
}

}

0 comments on commit 6c878b2

Please sign in to comment.