diff --git a/pd-notifier/audio/notification.html b/pd-notifier/audio/notification.html
new file mode 100644
index 0000000..928ec93
--- /dev/null
+++ b/pd-notifier/audio/notification.html
@@ -0,0 +1 @@
+
diff --git a/pd-notifier/background/pd-notifier.js b/pd-notifier/background/pd-notifier.js
deleted file mode 100644
index 7aa7309..0000000
--- a/pd-notifier/background/pd-notifier.js
+++ /dev/null
@@ -1,389 +0,0 @@
-// Simple script to poll PagerDuty API for new incidents, and trigger a Chrome notification for
-// any it finds. Will also give user ability to ack/resolve incidents right from the notifs.
-
-// Will poll continually at the pollInterval until it's destroyed (_destruct() is called).
-function PagerDutyNotifier()
-{
- // Members
- var self = this; // Self-reference
- self.account = null; // The PagerDuty account subdomain to check.
- self.apiKey = null; // Optional API key to not require active session.
- self.pollInterval = 15; // Number of seconds between checking for new notifications.
- self.includeLowUgency = false; // Whether to include low urgency incidents.
- self.removeButtons = false; // Whether or not to unclude the action buttons.
- self.openOnAck = false; // Whether to open the incident in a new tab when ack-ing.
- self.notifSound = false; // Whether to play a notification sound.
- self.requireInteraction = false; // Whether the notification will require user interaction to dismiss.
- self.filterServices = null; // ServiceID's of services to only show alerts for.
- self.filterUsers = null; // UserID's of users to only show alerts for.
- self.filterTeams = null; // TeamID's of teams to only show alerts for.
- self.pdapi = null; // Helper for API calls.
- self.poller = null; // This points to the interval function so we can clear it if needed.
- self.showBadgeUpdates = false; // Whether we show updates on the toolbar badge.
- self.badgeLocation = null; // Which view should be linked to from the badge icon.
-
- // Ctor
- self._construct = function _construct()
- {
- // Load in configuration, and then set up everything we need.
- self.loadConfiguration(function()
- {
- // If no account set up (first install), then do nothing. User will need to add
- // config. Once they save, a reload will be triggered and things will kick off.
- if (self.account == null || self.account == '') { return; }
-
- self.pdapi = new PDAPI(self.apiKey);
- self.setupPoller();
- });
- }
-
- // Dtor
- self._destruct = function _destruct()
- {
- clearInterval(self.poller);
- self = null;
- }
-
- // This loads any configuration we have stored with chrome.storage
- self.loadConfiguration = function loadConfiguration(callback)
- {
- chrome.storage.sync.get(
- {
- pdAccountSubdomain: '',
- pdAPIKey: null,
- pdIncludeLowUrgency: false,
- pdRemoveButtons: false,
- pdOpenOnAck: false,
- pdNotifSound: false,
- pdRequireInteraction: false,
- pdFilterServices: null,
- pdFilterUsers: null,
- pdFilterTeams: null,
- pdShowBadgeUpdates: false,
- pdBadgeLocation: 'triggered',
- },
- function(items)
- {
- self.account = items.pdAccountSubdomain;
- self.apiKey = items.pdAPIKey;
- self.includeLowUgency = items.pdIncludeLowUrgency;
- self.removeButtons = items.pdRemoveButtons;
- self.openOnAck = items.pdOpenOnAck;
- self.notifSound = items.pdNotifSound;
- self.requireInteraction = items.pdRequireInteraction;
- self.filterServices = items.pdFilterServices;
- self.filterUsers = items.pdFilterUsers;
- self.filterTeams = items.pdFilterTeams;
- self.showBadgeUpdates = items.pdShowBadgeUpdates;
- self.badgeLocation = items.pdBadgeLocation;
- callback(true);
- });
- }
-
- // This will set up the poller process.
- self.setupPoller = function setupPoller()
- {
- self.poller = setInterval(function() { self.polled(); }, self.pollInterval * 1000);
- self.polled();
- }
-
- // This is the method that's executed on each poll.
- self.polled = function polled()
- {
- self.pollNewIncidents();
- self.updateToolbarBadge();
- }
-
- // This will handle the event triggered from clicking one of the notification's buttons.
- self.handlerButtonClicked = function handlerButtonClicked(notificationId, buttonIndex)
- {
- if (notificationId == 'test') { return; } // Ignore for test notifications.
-
- switch (buttonIndex)
- {
- case 0: // Acknowledge
- self.pdapi.PUT(
- 'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
- '{"incident":{"type":"incident_reference","status":"acknowledged"}}'
- );
- if (self.openOnAck) { self.handlerNotificationClicked(notificationId); }
- break;
-
- case 1: // Resolve
- self.pdapi.PUT(
- 'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
- '{"incident":{"type":"incident_reference","status":"resolved"}}'
- );
- break;
- }
- setTimeout(function() { self.updateToolbarBadge(); }, 200); // Force a badge update, so it changes quickly.
- }
-
- // This will handle the event triggered when clicking on the main notification area.
- self.handlerNotificationClicked = function handlerNotificationClicked(notificationId)
- {
- if (notificationId == 'test') { return; } // Ignore for test notifications.
- window.open('https://' + self.account + '.pagerduty.com/incidents/' + notificationId);
- }
-
- // This is the poller action, which will trigger an API request and then pass any incidents
- // it gets to the parsing function.
- self.pollNewIncidents = function pollNewIncidents()
- {
- // Sanity check that an account has been set.
- if (self.account == '') { return; }
-
- // We only want events triggered since we last polled.
- var since = new Date();
- since.setSeconds(since.getSeconds() - self.pollInterval);
-
- // Construct the URL
- var url = 'https://' + self.account + '.pagerduty.com/api/v1/incidents?'
- + 'statuses[]=triggered&'
- + 'since=' + since.toISOString() + '&'
- + 'limit=5&'; // More than this would be silly to show notifications for.
- url = self.includeFilters(url);
-
- // Make the request.
- self.pdapi.GET(url, self.parseIncidents);
- }
-
- // Adds filters to a URL we'll be using in a request
- self.includeFilters = function includeFilters(url)
- {
- // Limit to high urgency if that's all the user wants.
- if (!self.includeLowUgency) { url = url + 'urgencies[]=high&'; }
-
- // Add a service filter if we have one.
- if (self.filterServices && self.filterServices != null && self.filterServices != "")
- {
- self.filterServices.split(',').forEach(function(s)
- {
- url = url + 'service_ids[]=' + s + '&';
- });
- }
-
- // Add a user filter if we have one.
- if (self.filterUsers && self.filterUsers != null && self.filterUsers != "")
- {
- self.filterUsers.split(',').forEach(function(s)
- {
- url = url + 'user_ids[]=' + s + '&';
- });
- }
-
- // Add a team filter if we have one.
- if (self.filterTeams && self.filterTeams != null && self.filterTeams != "")
- {
- self.filterTeams.split(',').forEach(function(s)
- {
- url = url + 'team_ids[]=' + s + '&';
- });
- }
-
- return url;
- }
-
- // This will parse the AJAX response and trigger notifications for each incident.
- self.parseIncidents = function parseIncidents(data)
- {
- for (var i in data.incidents) { self.triggerNotification(data.incidents[i]); }
- }
-
- // This will update the icon badge in the toolbar.
- self.updateToolbarBadge = function updateToolbarBadge()
- {
- if (!self.showBadgeUpdates)
- {
- chrome.browserAction.setBadgeText({ text: '' });
- return;
- }
-
- // Check for any triggered incidents at all that follow our filters.
- var url = self.includeFilters('https://' + self.account + '.pagerduty.com/api/v1/incidents?statuses[]=triggered&total=true&')
- self.pdapi.GET(url, function(data)
- {
- // If there was an error in the response, show "Err" and log it.
- if (data.error != null)
- {
- console.error("PagerDuty API returned an error while getting the incident count for the toolbar icon.", {
- api_url: url,
- error_returned: data.error
- });
- chrome.browserAction.setBadgeText({ text: 'Err.' });
- chrome.browserAction.setBadgeBackgroundColor({ color: [90, 90, 90, 255] });
- return;
- }
-
- // If there are no incidents, or an error in the response, show nothing on badge.
- if (data.total == null || data.total == 0)
- {
- chrome.browserAction.setBadgeText({ text: '' });
- return;
- }
-
- // Otherwise, we have incidents, show the count.
- chrome.browserAction.setBadgeText({ text: '' + data.total });
- chrome.browserAction.setBadgeBackgroundColor({ color: [189, 0, 0, 255] });
- });
- }
-
- // This will open a tab to the dashboard using the relevant user settings.
- self.openDashboard = function openDashboard()
- {
- // Determine the correct URL based on user's options.
- statuses = '';
- switch(self.badgeLocation)
- {
- case 'open': statuses = '?status=triggered,acknowledged'; break;
- case 'triggered': statuses = '?status=triggered'; break;
- case 'acknowledged': statuses = '?status=acknowledged'; break;
- case 'any': statuses = '?status=acknowledged,triggered,resolved'; break;
- }
-
- // Open the tab
- chrome.tabs.create({ 'url': 'https://' + self.account + '.pagerduty.com/incidents' + statuses })
- }
-
- // This will trigger the actual notification based on an incident object.
- self.triggerNotification = function triggerNotification(incident)
- {
- // Define the buttons to show in the notification. Will be empty if user asked to remove.
- var buttons = self.removeButtons ? [] : [
- {
- title: "Acknowledge",
- iconUrl: chrome.extension.getURL("images/icon-acknowledge.png")
- },
- {
- title: "Resolve",
- iconUrl: chrome.extension.getURL("images/icon-resolve.png")
- }
- ];
-
- chrome.notifications.create(incident.id,
- {
- type: "basic",
- iconUrl: chrome.extension.getURL("images/icon-256.png"),
- title: incident.summary,
- message: "Service: " + incident.service.summary,
- contextMessage: incident.urgency.charAt(0).toUpperCase() + incident.urgency.slice(1) + " Urgency",
- priority: 0,
- isClickable: true,
- buttons: buttons,
- requireInteraction: self.requireInteraction
- });
-
- // Trigger notification sound if user wants it.
- if (self.notifSound)
- {
- var notifSound = new Audio("audio/notification.mp3");
- notifSound.play();
- }
- }
-
- self._construct();
-}
-
-// Add event handlers for button/notification clicks, and delegate to the currently active notifier object.
-chrome.notifications.onButtonClicked.addListener(function(notificationId, buttonIndex)
-{
- chrome.runtime.getBackgroundPage(function(bgpg)
- {
- bgpg.getNotifier().handlerButtonClicked(notificationId, buttonIndex);
- chrome.notifications.clear(notificationId);
- });
-});
-chrome.notifications.onClicked.addListener(function(notificationId)
-{
- chrome.runtime.getBackgroundPage(function(bgpg)
- {
- bgpg.getNotifier().handlerNotificationClicked(notificationId);
- chrome.notifications.clear(notificationId);
- });
-});
-
-// Add event handler for the toolbar icon click.
-chrome.browserAction.onClicked.addListener(function(tab)
-{
- chrome.runtime.getBackgroundPage(function(bgpg)
- {
- bgpg.getNotifier().openDashboard();
- });
-});
-
-// If this is the first installation, show the options page so user can set up their settings.
-chrome.runtime.onInstalled.addListener(function(details)
-{
- if (details.reason == 'install')
- {
- chrome.tabs.create({ 'url': 'chrome://extensions/?options=' + chrome.runtime.id });
- }
-});
-
-// The currently active notifier object, and accessor.
-var _pdNotifier = null;
-function getNotifier() { return _pdNotifier; }
-
-// This will reload/trigger the the notifier (and pick up any new configuration options).
-function reloadNotifier()
-{
- if (_pdNotifier != null) { _pdNotifier._destruct(); }
- _pdNotifier = new PagerDutyNotifier();
-}
-
-// Add option to clear all notifications to icon context-menu.
-chrome.contextMenus.create({
- title: "Clear all notifications",
- id: "pd_clear_all",
- contexts: ["browser_action"],
- visible: true
-});
-
-chrome.contextMenus.onClicked.addListener(function(info, tab)
-{
- if (info.menuItemId === "pd_clear_all")
- {
- chrome.notifications.getAll(function(notifs)
- {
- for (var i in notifs) { chrome.notifications.clear(i); }
- });
- }
-});
-
-// Add option to trigger a test notification popup.
-chrome.contextMenus.create({
- title: "Show test notification",
- id: "pd_test_notification",
- contexts: ["browser_action"],
- visible: true
-});
-
-chrome.contextMenus.onClicked.addListener(function(info, tab)
-{
- if (info.menuItemId === "pd_test_notification")
- {
- _pdNotifier.triggerNotification({
- 'id': 'test',
- 'summary': 'Test Notification',
- 'service': {
- 'summary': 'Test Service'
- },
- 'urgency': 'high'
- })
- }
-});
-
-// Listen for Chrome Alarms and retrigger the notifier when one is caught.
-chrome.alarms.onAlarm.addListener(function(alarm)
-{
- chrome.runtime.getBackgroundPage(function(bgpg)
- {
- bgpg.reloadNotifier();
- });
-});
-
-// Sets up a Chrome Alarm to retrigger the notifier every so often, to make sure it's always running.
-chrome.alarms.create("pagerduty-notifier", { periodInMinutes: 1 });
-
-// Initial run, as alarm won't trigger immediately.
-reloadNotifier();
diff --git a/pd-notifier/lib/pd-api.js b/pd-notifier/lib/pd-api.js
index ceaf284..2a3dd16 100644
--- a/pd-notifier/lib/pd-api.js
+++ b/pd-notifier/lib/pd-api.js
@@ -1,55 +1,72 @@
// Helper wrappers for PagerDuty API methods.
-function PDAPI(apiKey, version = 2)
-{
+export default function PDAPI(apiKey, version = 2) {
// Members
- var self = this; // Self-reference
- self.apiKey = apiKey; // API key used for requests.
- self.userAgent = "pd-chrome-notifier-" + chrome.app.getDetails().version; // Will be in the X-Requested-With header of requests.
-
- // Wrapper for generic XMLHttpRequest stuff
- this.prepareRequest = function prepareRequest(method, url)
- {
- var xhr = new XMLHttpRequest();
- xhr.open(method, url, true);
- xhr.setRequestHeader("X-Requested-With", self.userAgent);
- xhr.setRequestHeader("X-PagerDuty-Api-Local", 1);
- xhr.setRequestHeader("Accept", "application/vnd.pagerduty+json;version=" + version);
+ var self = this; // Self-reference
+ self.apiKey = apiKey; // API key used for requests.
+ self.userAgent = "pd-chrome-notifier-" + chrome.runtime.getManifest().version; // Will be in the X-Requested-With header of requests.
+
+ // Perform a GET request, and trigger the callback with the result.
+ this.GET = function GET(url, callback, error_callback = null) {
+ const headers = new Headers();
+ headers.append('X-Requested-With', self.userAgent);
+ headers.append('X-PagerDuty-Api-Local', 1);
+ headers.append('Accept', "application/vnd.pagerduty+json;version=" + version);
// If we have a valid API key, authenticate using that.
- if (self.apiKey != null && self.apiKey.length == 20)
- {
- xhr.setRequestHeader("Authorization", "Token token=" + self.apiKey);
+ if (self.apiKey != null && self.apiKey.length == 20) {
+ headers.append('Authorization', 'Token token=' + self.apiKey);
}
- return xhr;
- }
+ const init = {
+ method: "GET",
+ headers: headers,
+ mode: "cors",
+ cache: "default"
+ };
- // Perform a GET request, and trigger the callback with the result.
- this.GET = function GET(url, callback, error_callback = null)
- {
- var req = self.prepareRequest("GET", url);
- req.onreadystatechange = function()
- {
- if (req.readyState == 4)
- {
- try
- {
- callback(JSON.parse(req.responseText));
- }
- catch(e)
- {
- if (error_callback != null) { error_callback(req.status, req.responseText); }
- }
+ fetch(url, init).then((res) => {
+ if (res.ok) {
+ res.json().then((data) => {
+ try {
+ callback(data);
+ }
+ catch (e) {
+ if (error_callback != null) { error_callback(res.statusText, res.statusText); }
+ }
+ });
+ } else {
+ console.error(res);
+ if (error_callback != null) { error_callback(res.statusText, res.statusText); }
}
- };
- req.send();
- }
+ });
+ };
// Fire and forget a PUT request.
- this.PUT = function PUT(url, data)
- {
- var req = self.prepareRequest("PUT", url);
- req.setRequestHeader("Content-Type", "application/json");
- req.send(data);
- }
+ this.PUT = function PUT(url, data) {
+ const headers = new Headers();
+ headers.append('X-Requested-With', self.userAgent);
+ headers.append('X-PagerDuty-Api-Local', 1);
+ headers.append('Accept', "application/vnd.pagerduty+json;version=" + version);
+
+ // If we have a valid API key, authenticate using that.
+ if (self.apiKey != null && self.apiKey.length == 20) {
+ headers.append('Authorization', 'Token token=' + self.apiKey);
+ }
+
+ headers.append('Content-Type', 'application/json');
+
+ const init = {
+ method: "PUT",
+ headers: headers,
+ mode: "cors",
+ cache: "default",
+ body: data
+ };
+
+ fetch(url, init).then((res) => {
+ if (!res.ok) {
+ console.error(res.status);
+ }
+ });
+ };
}
diff --git a/pd-notifier/lib/pd-notifier.js b/pd-notifier/lib/pd-notifier.js
new file mode 100644
index 0000000..ea0c4bf
--- /dev/null
+++ b/pd-notifier/lib/pd-notifier.js
@@ -0,0 +1,280 @@
+import PDAPI from "./pd-api.js";
+
+// Simple script to poll PagerDuty API for new incidents, and trigger a Chrome notification for
+// any it finds. Will also give user ability to ack/resolve incidents right from the notifs.
+
+// Will poll continually at the pollInterval until it's destroyed (_destruct() is called).
+export default function PagerDutyNotifier() {
+ // Members
+ var self = this; // Self-reference
+ self.account = null; // The PagerDuty account subdomain to check.
+ self.apiKey = null; // Optional API key to not require active session.
+ self.pollInterval = 15; // Number of seconds between checking for new notifications.
+ self.includeLowUgency = false; // Whether to include low urgency incidents.
+ self.removeButtons = false; // Whether or not to unclude the action buttons.
+ self.openOnAck = false; // Whether to open the incident in a new tab when ack-ing.
+ self.notifSound = false; // Whether to play a notification sound.
+ self.requireInteraction = false; // Whether the notification will require user interaction to dismiss.
+ self.filterServices = null; // ServiceID's of services to only show alerts for.
+ self.filterUsers = null; // UserID's of users to only show alerts for.
+ self.filterTeams = null; // TeamID's of teams to only show alerts for.
+ self.pdapi = null; // Helper for API calls.
+ self.poller = null; // This points to the interval function so we can clear it if needed.
+ self.showBadgeUpdates = false; // Whether we show updates on the toolbar badge.
+ self.badgeLocation = null; // Which view should be linked to from the badge icon.
+
+ // Ctor
+ self._construct = function _construct() {
+ // Load in configuration, and then set up everything we need.
+ self.loadConfiguration(function () {
+ // If no account set up (first install), then do nothing. User will need to add
+ // config. Once they save, a reload will be triggered and things will kick off.
+ if (self.account == null || self.account == '') { return; }
+
+ self.pdapi = new PDAPI(self.apiKey);
+ self.setupPoller();
+ });
+ };
+
+ // Dtor
+ self._destruct = function _destruct() {
+ clearInterval(self.poller);
+ self = null;
+ };
+
+ // This loads any configuration we have stored with chrome.storage
+ self.loadConfiguration = function loadConfiguration(callback) {
+ chrome.storage.sync.get(
+ {
+ pdAccountSubdomain: '',
+ pdAPIKey: null,
+ pdIncludeLowUrgency: false,
+ pdRemoveButtons: false,
+ pdOpenOnAck: false,
+ pdNotifSound: false,
+ pdRequireInteraction: false,
+ pdFilterServices: null,
+ pdFilterUsers: null,
+ pdFilterTeams: null,
+ pdShowBadgeUpdates: false,
+ pdBadgeLocation: 'triggered',
+ },
+ function (items) {
+ self.account = items.pdAccountSubdomain;
+ self.apiKey = items.pdAPIKey;
+ self.includeLowUgency = items.pdIncludeLowUrgency;
+ self.removeButtons = items.pdRemoveButtons;
+ self.openOnAck = items.pdOpenOnAck;
+ self.notifSound = items.pdNotifSound;
+ self.requireInteraction = items.pdRequireInteraction;
+ self.filterServices = items.pdFilterServices;
+ self.filterUsers = items.pdFilterUsers;
+ self.filterTeams = items.pdFilterTeams;
+ self.showBadgeUpdates = items.pdShowBadgeUpdates;
+ self.badgeLocation = items.pdBadgeLocation;
+ callback(true);
+ });
+ };
+
+ // This will set up the poller process.
+ self.setupPoller = function setupPoller() {
+ self.poller = setInterval(function () { self.polled(); }, self.pollInterval * 1000);
+ self.polled();
+ };
+
+ // This is the method that's executed on each poll.
+ self.polled = function polled() {
+ self.pollNewIncidents();
+ self.updateToolbarBadge();
+ };
+
+ // This will handle the event triggered from clicking one of the notification's buttons.
+ self.handlerButtonClicked = function handlerButtonClicked(notificationId, buttonIndex) {
+ if (notificationId == 'test') { return; } // Ignore for test notifications.
+
+ switch (buttonIndex) {
+ case 0: // Acknowledge
+ self.pdapi.PUT(
+ 'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
+ '{"incident":{"type":"incident_reference","status":"acknowledged"}}'
+ );
+ if (self.openOnAck) { self.handlerNotificationClicked(notificationId); }
+ break;
+
+ case 1: // Resolve
+ self.pdapi.PUT(
+ 'https://' + self.account + '.pagerduty.com/api/v1/incidents/' + notificationId,
+ '{"incident":{"type":"incident_reference","status":"resolved"}}'
+ );
+ break;
+ }
+ setTimeout(function () { self.updateToolbarBadge(); }, 200); // Force a badge update, so it changes quickly.
+ };
+
+ // This will handle the event triggered when clicking on the main notification area.
+ self.handlerNotificationClicked = function handlerNotificationClicked(notificationId) {
+ if (notificationId == 'test') { return; } // Ignore for test notifications.
+ // Open the tab
+ chrome.tabs.create({ 'url': 'https://' + self.account + '.pagerduty.com/incidents/' + notificationId });
+ };
+
+ // This is the poller action, which will trigger an API request and then pass any incidents
+ // it gets to the parsing function.
+ self.pollNewIncidents = function pollNewIncidents() {
+ // Sanity check that an account has been set.
+ if (self.account == '') { return; }
+
+ // We only want events triggered since we last polled.
+ var since = new Date();
+ since.setSeconds(since.getSeconds() - self.pollInterval);
+
+ // Construct the URL
+ var url = 'https://' + self.account + '.pagerduty.com/api/v1/incidents?'
+ + 'statuses[]=triggered&'
+ + 'since=' + since.toISOString() + '&'
+ + 'limit=5&'; // More than this would be silly to show notifications for.
+ url = self.includeFilters(url);
+
+ // Make the request.
+ self.pdapi.GET(url, self.parseIncidents);
+ };
+
+ // Adds filters to a URL we'll be using in a request
+ self.includeFilters = function includeFilters(url) {
+ // Limit to high urgency if that's all the user wants.
+ if (!self.includeLowUgency) { url = url + 'urgencies[]=high&'; }
+
+ // Add a service filter if we have one.
+ if (self.filterServices && self.filterServices != null && self.filterServices != "") {
+ self.filterServices.split(',').forEach(function (s) {
+ url = url + 'service_ids[]=' + s + '&';
+ });
+ }
+
+ // Add a user filter if we have one.
+ if (self.filterUsers && self.filterUsers != null && self.filterUsers != "") {
+ self.filterUsers.split(',').forEach(function (s) {
+ url = url + 'user_ids[]=' + s + '&';
+ });
+ }
+
+ // Add a team filter if we have one.
+ if (self.filterTeams && self.filterTeams != null && self.filterTeams != "") {
+ self.filterTeams.split(',').forEach(function (s) {
+ url = url + 'team_ids[]=' + s + '&';
+ });
+ }
+
+ return url;
+ };
+
+ // This will parse the AJAX response and trigger notifications for each incident.
+ self.parseIncidents = function parseIncidents(data) {
+ for (var i in data.incidents) {
+ self.triggerNotification(data.incidents[i]);
+ }
+ };
+
+ // This will update the icon badge in the toolbar.
+ self.updateToolbarBadge = function updateToolbarBadge() {
+ if (!self.showBadgeUpdates) {
+ chrome.action.setBadgeText({ text: '' });
+ return;
+ }
+
+ // Check for any triggered incidents at all that follow our filters.
+ var url = self.includeFilters('https://' + self.account + '.pagerduty.com/api/v1/incidents?statuses[]=triggered&total=true&');
+ self.pdapi.GET(url, function (data) {
+ // If there was an error in the response, show "Err" and log it.
+ if (data.error != null) {
+ console.error("PagerDuty API returned an error while getting the incident count for the toolbar icon.", {
+ api_url: url,
+ error_returned: data.error
+ });
+ chrome.action.setBadgeText({ text: 'Err.' });
+ chrome.action.setBadgeBackgroundColor({ color: [90, 90, 90, 255] });
+ return;
+ }
+
+ // If there are no incidents, or an error in the response, show nothing on badge.
+ if (data.total == null || data.total == 0) {
+ chrome.action.setBadgeText({ text: '' });
+ return;
+ }
+
+ // Otherwise, we have incidents, show the count.
+ chrome.action.setBadgeText({ text: '' + data.total });
+ chrome.action.setBadgeBackgroundColor({ color: [189, 0, 0, 255] });
+ });
+ };
+
+ // This will open a tab to the dashboard using the relevant user settings.
+ self.openDashboard = function openDashboard() {
+ // Determine the correct URL based on user's options.
+ let statuses = '';
+ switch (self.badgeLocation) {
+ case 'open': statuses = '?status=triggered,acknowledged'; break;
+ case 'triggered': statuses = '?status=triggered'; break;
+ case 'acknowledged': statuses = '?status=acknowledged'; break;
+ case 'any': statuses = '?status=acknowledged,triggered,resolved'; break;
+ }
+
+ // Open the tab
+ chrome.tabs.create({ 'url': 'https://' + self.account + '.pagerduty.com/incidents' + statuses });
+ };
+
+ // This will trigger the actual notification based on an incident object.
+ self.triggerNotification = function triggerNotification(incident) {
+ // Define the buttons to show in the notification. Will be empty if user asked to remove.
+ var buttons = self.removeButtons ? [] : [
+ {
+ title: "Acknowledge",
+ iconUrl: chrome.runtime.getURL("images/icon-acknowledge.png")
+ },
+ {
+ title: "Resolve",
+ iconUrl: chrome.runtime.getURL("images/icon-resolve.png")
+ }
+ ];
+
+ chrome.notifications.create(incident.id,
+ {
+ type: "basic",
+ iconUrl: chrome.runtime.getURL("images/icon-256.png"),
+ title: incident.summary,
+ message: "Service: " + incident.service.summary,
+ contextMessage: incident.urgency.charAt(0).toUpperCase() + incident.urgency.slice(1) + " Urgency",
+ priority: 0,
+ isClickable: true,
+ buttons: buttons,
+ requireInteraction: self.requireInteraction
+ });
+
+ // Trigger notification sound if user wants it.
+ if (self.notifSound) {
+ self.playNotification();
+ }
+ };
+
+ // Creates an offscreen document to play the audio notification
+ self.playNotification = async function () {
+ try {
+ if (await chrome.offscreen.hasDocument()) {
+ await chrome.offscreen.closeDocument();
+ self.playNotification();
+ return;
+ }
+ await chrome.offscreen.createDocument({
+ url: chrome.runtime.getURL('../audio/notification.html'),
+ reasons: ['AUDIO_PLAYBACK'],
+ justification: 'Play PagerDuty notification sound',
+ });
+ } catch (e) {
+ // When lots of notifications come in at once
+ // they try to create lots of offscreen documents
+ // but we can only create one, and that's all we need
+ }
+ };
+
+ self._construct();
+}
diff --git a/pd-notifier/manifest.json b/pd-notifier/manifest.json
index f9771df..cb95cbf 100644
--- a/pd-notifier/manifest.json
+++ b/pd-notifier/manifest.json
@@ -1,37 +1,35 @@
{
- "manifest_version": 2,
- "name": "PagerDuty Notifier",
- "short_name": "PD Notifier",
- "description": "Desktop notifications for your PagerDuty incidents.",
- "version": "0.23",
- "author": "Rich Adams (https://richadams.me)",
- "icons": {
- "16": "images/icon-16.png",
- "32": "images/icon-32.png",
- "64": "images/icon-64.png",
- "128": "images/icon-128.png",
- "256": "images/icon-256.png"
- },
- "permissions": [
- "notifications",
- "background",
- "storage",
- "alarms",
- "https://*.pagerduty.com/api/v1/*",
- "contextMenus"
- ],
- "options_ui": {
- "open_in_tab": true,
- "page": "options/options.html"
- },
- "background": {
- "scripts": [
- "lib/pd-api.js",
- "background/pd-notifier.js"
- ],
- "persistent": true
- },
- "browser_action": {
- "default_icon": "images/browser-icon-32.png"
- }
+ "manifest_version": 3,
+ "name": "PagerDuty Notifier",
+ "short_name": "PD Notifier",
+ "description": "Desktop notifications for your PagerDuty incidents.",
+ "version": "0.23",
+ "author": "Rich Adams (https://richadams.me)",
+ "icons": {
+ "16": "images/icon-16.png",
+ "32": "images/icon-32.png",
+ "64": "images/icon-64.png",
+ "128": "images/icon-128.png",
+ "256": "images/icon-256.png"
+ },
+ "permissions": [
+ "notifications",
+ "background",
+ "storage",
+ "alarms",
+ "contextMenus",
+ "offscreen"
+ ],
+ "host_permissions": ["https://*.pagerduty.com/api/v1/*"],
+ "options_ui": {
+ "open_in_tab": true,
+ "page": "options/options.html"
+ },
+ "background": {
+ "service_worker": "service_worker.js",
+ "type": "module"
+ },
+ "action": {
+ "default_icon": "images/browser-icon-32.png"
+ }
}
diff --git a/pd-notifier/options/api-validate.js b/pd-notifier/options/api-validate.js
index 93151e8..378ba25 100644
--- a/pd-notifier/options/api-validate.js
+++ b/pd-notifier/options/api-validate.js
@@ -1,100 +1,86 @@
+import PDAPI from '../lib/pd-api.js';
// These functions will allow us to validate the API access, so people can self-debug any incorrect credentials.
// Should be exactly 20 chars long.
-function isKeyFormatValid()
-{
+function isKeyFormatValid() {
e = getElement('api-key');
return (e.value == "" || e.value.length == 20);
}
// Status message updater helpers.
-function statusError(message = '')
-{
+function statusError(message = '') {
getElement('access-status').className = 'ko';
getElement('access-status').innerHTML = '✗ ' + message;
}
-function statusOK(message = '')
-{
+function statusOK(message = '') {
getElement('access-status').className = 'ok';
getElement('access-status').innerHTML = '✓ ' + message;
}
-function statusWarning(message = '')
-{
+function statusWarning(message = '') {
getElement('access-status').className = 'warn';
getElement('access-status').innerHTML = '⚠ ' + message;
}
-function validateAPIKeyAccess(apiKey)
-{
+function validateAPIKeyAccess(apiKey) {
// Validate if they can access v2 API endpoint using API Key.
var v2api = new PDAPI(apiKey);
- try
- {
+ try {
v2api.GET('https://api.pagerduty.com/users?limit=1',
- // If so, the API key is valid (for either v1 or v2)
- function success(data)
- {
- statusOK();
- },
+ // If so, the API key is valid (for either v1 or v2)
+ function success(data) {
+ statusOK();
+ },
- // If we get to here, then API key is invalid (or not supplied). Validate if cookie auth is working.
- function error(status, data)
- {
- // Don't pass an API key, will attempt cookie auth using their subdomain.
- var v1api = new PDAPI('', 1);
- v1api.GET('https://' + getElement('account-subdomain').value + '.pagerduty.com/api/v1/users?limit=1',
- function success(data)
- {
- // If HTTP200 and 2007 error code returned, then subdomain is invalid. Flag that up.
- try
- {
- if (data.error.code == 2007) { statusError("Account not valid, check subdomain"); return; }
- }
- catch(e) {}
+ // If we get to here, then API key is invalid (or not supplied). Validate if cookie auth is working.
+ function error(status, data) {
+ // Don't pass an API key, will attempt cookie auth using their subdomain.
+ var v1api = new PDAPI('', 1);
+ v1api.GET('https://' + getElement('account-subdomain').value + '.pagerduty.com/api/v1/users?limit=1',
+ function success(data) {
+ // If HTTP200 and 2007 error code returned, then subdomain is invalid. Flag that up.
+ try {
+ if (data.error.code == 2007) { statusError("Account not valid, check subdomain"); return; }
+ }
+ catch (e) { }
- // Otherwise, cookie auth is good.
- statusWarning("Invalid API key! (Cookie auth is OK)");
- },
- function error(status, data)
- {
- // Cookie auth was bad, enumerate the reasons.
- if (status == 401) { statusError("Invalid API key! (Not logged in for cookie auth)"); return; }
- if (status == 403) { statusError("API key valid, but you are not authorized to access the API"); return; }
- if (status == 404) { statusError("Account not valid, check subdomain"); return; }
- statusError("Access Denied (" + status + "). Unable to access using API key or cookie auth");
+ // Otherwise, cookie auth is good.
+ statusWarning("Invalid API key! (Cookie auth is OK)");
+ },
+ function error(status, data) {
+ // Cookie auth was bad, enumerate the reasons.
+ if (status == 401) { statusError("Invalid API key! (Not logged in for cookie auth)"); return; }
+ if (status == 403) { statusError("API key valid, but you are not authorized to access the API"); return; }
+ if (status == 404) { statusError("Account not valid, check subdomain"); return; }
+ statusError("Access Denied (" + status + "). Unable to access using API key or cookie auth");
+ });
});
- });
}
- catch(e)
- {
+ catch (e) {
statusError("Invalid API key! (Contains invalid chars?)");
}
}
// Add ability to validate API key access, so people can test themselves.
-document.getElementById('api-validate').addEventListener('click', function ()
-{
+document.getElementById('api-validate').addEventListener('click', function () {
// Clear any old status
getElement('access-status').className = 'loading';
getElement('access-status').innerHTML = '';
// If API key in field is new, use that.
- if (!isAPIKeyObfuscated(getElement('api-key').value))
- {
+ if (!isAPIKeyObfuscated(getElement('api-key').value)) {
validateAPIKeyAccess(getElement('api-key').value);
return;
}
// Otherwise, use the API key we have in storage.
chrome.storage.sync.get(
- {
- pdAPIKey: '',
- },
- function(items)
- {
- validateAPIKeyAccess(items.pdAPIKey);
- });
+ {
+ pdAPIKey: '',
+ },
+ function (items) {
+ validateAPIKeyAccess(items.pdAPIKey);
+ });
});
diff --git a/pd-notifier/options/options.html b/pd-notifier/options/options.html
index 842a360..56c988b 100644
--- a/pd-notifier/options/options.html
+++ b/pd-notifier/options/options.html
@@ -1,17 +1,19 @@
+
PagerDuty Notifier Configuration
+
Notifier Configuration
Account and Authentication
-
+
https://.pagerduty.com
@@ -20,43 +22,51 @@
Account and Authentication
- Optionally provide an API key for your PagerDuty account. [20 characters]
+ Optionally provide an API
+ key for your PagerDuty account. [20 characters]
- If you don't provide an API key, you will only receive notifications when your website session is active.
+ If you don't provide an API key, you will only receive notifications when your website session is active.
- If you provide a read-only API key, you will get notifications, but you will be unable to acknowledge/resolve them from the notification. Enable the "Remove Acknowledge & Resolve buttons?" customization option below in that case.
+ If you provide a read-only API key, you will get notifications, but you will be unable to
+ acknowledge/resolve them from the notification. Enable the "Remove Acknowledge & Resolve buttons?"
+ customization option below in that case.
-
+
Notification Filters
- Only show notifications for incidents currently assigned to these user(s). Should be one or more user IDs (comma separated, no spaces).
+ Only show notifications for incidents currently assigned to these user(s). Should be one
+ or more user IDs (comma separated, no spaces).
- Only show notifications for incidents in escalation policies that belong to these team(s). Should be one or more team IDs (comma separated, no spaces).
+ Only show notifications for incidents in escalation policies that belong to these
+ team(s). Should be one or more team IDs (comma separated, no spaces).
- Only show notifications for incidents from these services. Should be one or more service IDs (comma separated, no spaces).
+ Only show notifications for incidents from these services. Should be one or more service
+ IDs (comma separated, no spaces).
- All filters must be satisfied for you to get a notification. So if you filter by both a user and service, the incident must be in that service and belong to that user for you to get the notification.
+ All filters must be satisfied for you to get a notification. So if you filter by both a user and service,
+ the incident must be in that service and belong to that user for you to get the notification.