Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pd-notifier/audio/notification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="./notification.js"></script>
17 changes: 17 additions & 0 deletions pd-notifier/audio/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Add listener for relevant message to trigger notification sound.
chrome.runtime.onMessage.addListener(function(message)
{
if (message == 'play-notification') { playNotifierNotificationSound(); }
});

// Helper function to create the actual notification sound.
function playNotifierNotificationSound()
{
var notifSound = new Audio("/audio/notification.mp3");
notifSound.play();
}

// We also want to play the sound when the offscreen document is first loaded.
// Otherwise the very first load doesn't actually play the sound as the message arrives
// before the handler is configured.
playNotifierNotificationSound();
102 changes: 59 additions & 43 deletions pd-notifier/background/pd-notifier.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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.

importScripts('/lib/pd-api.js');

// Will poll continually at the pollInterval until it's destroyed (_destruct() is called).
function PagerDutyNotifier()
{
Expand Down Expand Up @@ -123,7 +125,7 @@ function PagerDutyNotifier()
self.handlerNotificationClicked = function handlerNotificationClicked(notificationId)
{
if (notificationId == 'test') { return; } // Ignore for test notifications.
window.open('https://' + self.account + '.pagerduty.com/incidents/' + notificationId);
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
Expand Down Expand Up @@ -195,7 +197,7 @@ function PagerDutyNotifier()
{
if (!self.showBadgeUpdates)
{
chrome.browserAction.setBadgeText({ text: '' });
chrome.action.setBadgeText({ text: '' });
return;
}

Expand All @@ -210,21 +212,21 @@ function PagerDutyNotifier()
api_url: url,
error_returned: data.error
});
chrome.browserAction.setBadgeText({ text: 'Err.' });
chrome.browserAction.setBadgeBackgroundColor({ color: [90, 90, 90, 255] });
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.browserAction.setBadgeText({ text: '' });
chrome.action.setBadgeText({ text: '' });
return;
}

// Otherwise, we have incidents, show the count.
chrome.browserAction.setBadgeText({ text: '' + data.total });
chrome.browserAction.setBadgeBackgroundColor({ color: [189, 0, 0, 255] });
chrome.action.setBadgeText({ text: '' + data.total });
chrome.action.setBadgeBackgroundColor({ color: [189, 0, 0, 255] });
});
}

Expand Down Expand Up @@ -252,18 +254,18 @@ function PagerDutyNotifier()
var buttons = self.removeButtons ? [] : [
{
title: "Acknowledge",
iconUrl: chrome.extension.getURL("images/icon-acknowledge.png")
iconUrl: chrome.runtime.getURL("images/icon-acknowledge.png")
},
{
title: "Resolve",
iconUrl: chrome.extension.getURL("images/icon-resolve.png")
iconUrl: chrome.runtime.getURL("images/icon-resolve.png")
}
];

chrome.notifications.create(incident.id,
{
type: "basic",
iconUrl: chrome.extension.getURL("images/icon-256.png"),
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",
Expand All @@ -274,11 +276,27 @@ function PagerDutyNotifier()
});

// Trigger notification sound if user wants it.
if (self.notifSound)
if (self.notifSound) { self.playNotification(); }
}

// Play notification sound by sending message to an offscreen document to trigger it.
// In the move from MV2 to MV3, audio doesn't work directly from here any more.
self.playNotification = function playNotification()
{
self.createNotificationDocument();
chrome.runtime.sendMessage('play-notification');
}

// Create an offscreen document for the audio notification.
self.createNotificationDocument = async function createNotificationDocument()
{
if (await chrome.offscreen.hasDocument()) return;
await chrome.offscreen.createDocument(
{
var notifSound = new Audio("audio/notification.mp3");
notifSound.play();
}
url: chrome.runtime.getURL('audio/notification.html'),
reasons: ['AUDIO_PLAYBACK'],
justification: 'Play notification sound at user request.'
});
}

self._construct();
Expand All @@ -287,28 +305,19 @@ function PagerDutyNotifier()
// 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);
});
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);
});
getNotifier().handlerNotificationClicked(notificationId);
chrome.notifications.clear(notificationId);
});

// Add event handler for the toolbar icon click.
chrome.browserAction.onClicked.addListener(function(tab)
chrome.action.onClicked.addListener(function(tab)
{
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.getNotifier().openDashboard();
});
getNotifier().openDashboard();
});

// If this is the first installation, show the options page so user can set up their settings.
Expand All @@ -320,6 +329,12 @@ chrome.runtime.onInstalled.addListener(function(details)
}
});

// Listen for reload messages and reload the notifier if receieved.
chrome.runtime.onMessage.addListener(function(message)
{
if (message == 'reload-pd-notifier') { reloadNotifier(); }
});

// The currently active notifier object, and accessor.
var _pdNotifier = null;
function getNotifier() { return _pdNotifier; }
Expand All @@ -332,11 +347,13 @@ function reloadNotifier()
}

// 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.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
title: "Clear all notifications",
id: "pd_clear_all",
contexts: ["action"],
visible: true
});
});

chrome.contextMenus.onClicked.addListener(function(info, tab)
Expand All @@ -351,11 +368,13 @@ chrome.contextMenus.onClicked.addListener(function(info, tab)
});

// 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.runtime.onInstalled.addListener(function() {
chrome.contextMenus.create({
title: "Show test notification",
id: "pd_test_notification",
contexts: ["action"],
visible: true
});
});

chrome.contextMenus.onClicked.addListener(function(info, tab)
Expand All @@ -376,10 +395,7 @@ chrome.contextMenus.onClicked.addListener(function(info, tab)
// 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();
});
reloadNotifier();
});

// Sets up a Chrome Alarm to retrigger the notifier every so often, to make sure it's always running.
Expand Down
67 changes: 42 additions & 25 deletions pd-notifier/lib/pd-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,69 @@ 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.
self.userAgent = "pd-chrome-notifier-" + chrome.runtime.getManifest().version; // Will be in the X-Requested-With header of requests.

// Wrapper for generic XMLHttpRequest stuff
this.prepareRequest = function prepareRequest(method, url)
// Prepare the headers for each request type.
this.prepareHeaders = function prepareHeaders()
{
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 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);
headers.append("Authorization", "Token token=" + self.apiKey);
}

return xhr;
return headers;
}

// 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()
var options = {
method: "GET",
headers: self.prepareHeaders()
}

fetch(url, options).then((res) =>
{
if (req.readyState == 4)
// Success
if (res.ok)
{
try
{
callback(JSON.parse(req.responseText));
}
catch(e)
res.json().then((data) =>
{
if (error_callback != null) { error_callback(req.status, req.responseText); }
}
try
{
callback(data);
}
catch (e)
{
if (error_callback != null) { error_callback(res.status, res.text()); }
}
});
}
};
req.send();
// Non-200 response code.
else
{
if (error_callback != null) { error_callback(res.status, res.statusText); }
}
});
}

// 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);
// We need to add a content-type when making outbound requests.
var headers = self.prepareHeaders();
headers.append("Content-Type", "application/json");

fetch(url, {
method: "PUT",
headers: headers,
body: data
});
}
}
21 changes: 10 additions & 11 deletions pd-notifier/manifest.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"manifest_version": 2,
"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)",
"version": "0.24",
"author": "Rich Adams",
"icons": {
"16": "images/icon-16.png",
"32": "images/icon-32.png",
Expand All @@ -17,21 +17,20 @@
"background",
"storage",
"alarms",
"https://*.pagerduty.com/api/v1/*",
"contextMenus"
"contextMenus",
"offscreen"
],
"host_permissions": [
"https://*.pagerduty.com/api/v1/*"
],
"options_ui": {
"open_in_tab": true,
"page": "options/options.html"
},
"background": {
"scripts": [
"lib/pd-api.js",
"background/pd-notifier.js"
],
"persistent": true
"service_worker": "background/pd-notifier.js"
},
"browser_action": {
"action": {
"default_icon": "images/browser-icon-32.png"
}
}
2 changes: 1 addition & 1 deletion pd-notifier/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ <h2>Customizations</h2>
<p>&#x2713; Changes saved!</p>
</section>

<script src="../lib/pd-api.js"></script>
<script src="/lib/pd-api.js"></script>
<script src="options.js"></script>
<script src="api-validate.js"></script>
</body>
Expand Down
7 changes: 2 additions & 5 deletions pd-notifier/options/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,14 @@ document.getElementById('save').addEventListener('click', function ()
function()
{
// Tell the notifier to reload itself with the latest configuration.
chrome.runtime.getBackgroundPage(function(bgpg)
{
bgpg.reloadNotifier();
});
chrome.runtime.sendMessage('reload-pd-notifier');

// Let the user know things saved properly.
getElement('saved').className = 'saved';
setTimeout(function() { getElement('saved').className = ''; }, 3000);

// Remove badge icon if it was previously set.
if (!getElement('show-badge').checked) { chrome.browserAction.setBadgeText({ text: '' }); }
if (!getElement('show-badge').checked) { chrome.action.setBadgeText({ text: '' }); }
});
});

Expand Down