From 07af7cd4490c16a72cd9155758d6e2dfc7a19efc Mon Sep 17 00:00:00 2001
From: Andrew Lisowski
Date: Sat, 28 Apr 2018 19:58:12 -0700
Subject: [PATCH 1/3] allow user to enable on a domain. enables github
enterprise
---
package.json | 5 +-
src/background.js | 6 +-
src/manifest.json | 10 +-
src/vendor/webext-domain-permission-toggle.js | 49 ++++++++
src/vendor/webext-dynamic-content-scripts.js | 113 ++++++++++++++++++
5 files changed, 180 insertions(+), 3 deletions(-)
create mode 100644 src/vendor/webext-domain-permission-toggle.js
create mode 100644 src/vendor/webext-dynamic-content-scripts.js
diff --git a/package.json b/package.json
index bca73c5..04699c0 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,10 @@
"version": "1.0.0",
"description": "Show github contributor's stats (# of Issues/PRs)",
"main": "index.js",
- "dependencies": {},
+ "dependencies": {
+ "webext-domain-permission-toggle": "0.0.2",
+ "webext-dynamic-content-scripts": "^5.0.1"
+ },
"devDependencies": {
"chrome-webstore-upload-cli": "^1.0.3"
},
diff --git a/src/background.js b/src/background.js
index 0286869..df55384 100644
--- a/src/background.js
+++ b/src/background.js
@@ -1,4 +1,8 @@
-/* global chrome */
+/* global chrome DCE DCS */
+
+// GitHub Enterprise support
+DCE.addContextMenu();
+DCS.addToFutureTabs();
// launch options page on first run
chrome.runtime.onInstalled.addListener((details) => {
diff --git a/src/manifest.json b/src/manifest.json
index 6e200ac..b2a2e4f 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -11,11 +11,19 @@
"permissions": [
"https://github.com/*/*",
"storage",
- "identity"
+ "identity",
+ "contextMenus",
+ "activeTab"
+ ],
+ "optional_permissions": [
+ "http://*/*",
+ "https://*/*"
],
"background": {
"persistent": false,
"scripts": [
+ "vendor/webext-domain-permission-toggle.js",
+ "vendor/webext-dynamic-content-scripts.js",
"background.js"
]
},
diff --git a/src/vendor/webext-domain-permission-toggle.js b/src/vendor/webext-domain-permission-toggle.js
new file mode 100644
index 0000000..bea7d07
--- /dev/null
+++ b/src/vendor/webext-domain-permission-toggle.js
@@ -0,0 +1,49 @@
+// https://github.com/bfred-it/webext-domain-permission-toggle
+
+const DCE = {
+ addContextMenu(options) {
+ const contextMenuId = 'webext-domain-permission-toggle:add-permission';
+
+ const {name: extensionName} = chrome.runtime.getManifest();
+
+ options = Object.assign({
+ title: `Enable ${extensionName} on this domain`,
+ reloadOnSuccess: `Do you want to reload this page to apply ${extensionName}?`
+ }, options);
+
+ // This has to happen onInstalled in Event Pages
+ chrome.runtime.onInstalled.addListener(() => {
+ chrome.contextMenus.create({
+ id: contextMenuId,
+ title: options.title,
+ contexts: ['page_action', 'browser_action'],
+ documentUrlPatterns: [
+ 'http://*/*',
+ 'https://*/*'
+ ]
+ });
+ });
+
+ chrome.contextMenus.onClicked.addListener(async ({menuItemId}, {tabId, url}) => {
+ if (menuItemId !== contextMenuId) {
+ return;
+ }
+ chrome.permissions.request({
+ origins: [
+ `${new URL(url).origin}/*`
+ ]
+ }, granted => {
+ if (chrome.runtime.lastError) {
+ console.error(`Error: ${chrome.runtime.lastError.message}`);
+ alert(`Error: ${chrome.runtime.lastError.message}`);
+ } else if (granted && options.reloadOnSuccess && confirm(options.reloadOnSuccess)) {
+ chrome.tabs.reload(tabId);
+ }
+ });
+ });
+ }
+};
+
+if (typeof module === 'object') {
+ module.exports = DCE;
+}
diff --git a/src/vendor/webext-dynamic-content-scripts.js b/src/vendor/webext-dynamic-content-scripts.js
new file mode 100644
index 0000000..62b748f
--- /dev/null
+++ b/src/vendor/webext-dynamic-content-scripts.js
@@ -0,0 +1,113 @@
+// https://github.com/bfred-it/webext-dynamic-content-scripts
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.DCS = global.DCS || {})));
+}(this, (function (exports) { 'use strict';
+
+function interopDefault(ex) {
+ return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
+}
+
+function createCommonjsModule(fn, module) {
+ return module = { exports: {} }, fn(module, module.exports), module.exports;
+}
+
+var webextContentScriptPing = createCommonjsModule(function (module) {
+// https://github.com/bfred-it/webext-content-script-ping
+
+/**
+ * Ping responder
+ */
+document.__webextContentScriptLoaded = true;
+
+/**
+ * Pinger
+ */
+function pingContentScript(tab) {
+ return new Promise((resolve, reject) => {
+ chrome.tabs.executeScript(tab.id || tab, {
+ code: 'document.__webextContentScriptLoaded',
+ runAt: 'document_start'
+ }, hasScriptAlready => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ } else {
+ resolve(Boolean(hasScriptAlready[0]));
+ }
+ });
+ });
+}
+
+if (typeof module === 'object') {
+ module.exports = pingContentScript;
+}
+});
+
+var pingContentScript = interopDefault(webextContentScriptPing);
+
+async function p(fn, ...args) {
+ return new Promise((resolve, reject) => fn(...args, r => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ } else {
+ resolve(r);
+ }
+ }));
+}
+
+async function addToTab(tab, contentScripts) {
+ if (typeof tab !== 'object' && typeof tab !== 'number') {
+ throw new TypeError('Specify a Tab or tabId');
+ }
+
+ if (!contentScripts) {
+ // Get all scripts from manifest.json
+ contentScripts = chrome.runtime.getManifest().content_scripts;
+ } else if (!Array.isArray(contentScripts)) {
+ // Single script object, make it an array
+ contentScripts = [contentScripts];
+ }
+
+ try {
+ const tabId = tab.id || tab;
+ if (!await pingContentScript(tabId)) {
+ const injections = [];
+ for (const group of contentScripts) {
+ const allFrames = group.all_frames;
+ const runAt = group.run_at;
+ for (const file of group.css) {
+ injections.push(p(chrome.tabs.insertCSS, tabId, {file, allFrames, runAt}));
+ }
+ for (const file of group.js) {
+ injections.push(p(chrome.tabs.executeScript, tabId, {file, allFrames, runAt}));
+ }
+ }
+ return Promise.all(injections);
+ }
+ } catch (err) {
+ // Probably the domain isn't permitted.
+ // It's easier to catch this than do 2 queries
+ }
+}
+
+function addToFutureTabs(scripts) {
+ chrome.tabs.onUpdated.addListener((tabId, {status}) => {
+ if (status === 'loading') {
+ addToTab(tabId, scripts);
+ }
+ });
+}
+
+var index = {
+ addToTab,
+ addToFutureTabs
+};
+
+exports.addToTab = addToTab;
+exports.addToFutureTabs = addToFutureTabs;
+exports['default'] = index;
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+})));
From b3c196abf6721ee2a228a4bf8789ce5418842c73 Mon Sep 17 00:00:00 2001
From: Andrew Lisowski
Date: Sat, 28 Apr 2018 20:22:09 -0700
Subject: [PATCH 2/3] have to use the right api on enterprise urls
---
package-lock.json | 26 ++++++++++++++++++++++++++
src/content.js | 7 +++++--
2 files changed, 31 insertions(+), 2 deletions(-)
create mode 100644 package-lock.json
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..509cbbf
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,26 @@
+{
+ "name": "github-contributor-extension",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "webext-content-script-ping": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/webext-content-script-ping/-/webext-content-script-ping-2.0.1.tgz",
+ "integrity": "sha512-37oaeT0GwwkIC8JUD5aToOr0ncdoml/kjZdsn5ckyKyZqpNQHYxhP3NhnqsSx25LpxGfJm8er/IPv9pzCLcfUA=="
+ },
+ "webext-domain-permission-toggle": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/webext-domain-permission-toggle/-/webext-domain-permission-toggle-0.0.2.tgz",
+ "integrity": "sha512-obPAEocq/5uw0jaKc1N2bwux/HXDaYz9/8ltvxTRqr0iR6JaTh6/h56FL9uoAZYXcXxjFyE+PRrJicoQrJ8T6g=="
+ },
+ "webext-dynamic-content-scripts": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/webext-dynamic-content-scripts/-/webext-dynamic-content-scripts-5.0.1.tgz",
+ "integrity": "sha512-R0TPOV9B5hDnSlOTaxxEkuX3L8rm+8KQSWpdxAqE+yYv/YqaDXip+8M39v00jXGTkpz36YdAnGjRdnzHw7taOw==",
+ "requires": {
+ "webext-content-script-ping": "2.0.1"
+ }
+ }
+ }
+}
diff --git a/src/content.js b/src/content.js
index 77390fd..5d4f223 100644
--- a/src/content.js
+++ b/src/content.js
@@ -8,6 +8,9 @@ const getCurrentUser = () => $(".js-menu-target img").attr("alt").slice(1) || ""
const isPrivate = () => $(".label-private").length > 0;
let statsScope = "repo";
+const githubURLBase = window.location.hostname === 'github.com' ? 'api.github.com' : `${window.location.hostname}/api/v3`;
+const githubURL = `https://${githubURLBase}`
+
function getContributor() {
let $contributor = $(".timeline-comment-wrapper .timeline-comment-header-text strong a");
if ($contributor.length) {
@@ -71,10 +74,10 @@ function contributorCount({access_token, contributor, user, repoPath, old = {},
repo = undefined;
repoPath = "__self";
}
-
+
let searchURL = buildUrl({
access_token,
- base: "https://api.github.com/search/issues",
+ base: `${githubURL}/search/issues`,
order: "asc",
per_page: "1",
q: {
From 807d847b61b44f1970bc2a7f37adcbf41c3eb72f Mon Sep 17 00:00:00 2001
From: Andrew Lisowski
Date: Sat, 28 Apr 2018 20:31:36 -0700
Subject: [PATCH 3/3] need to have seperate field for enterprise token. might
want to expand this in the future to allow for an access token for each
domain
---
src/content.js | 15 ++++++++++-----
src/options.html | 2 ++
src/options.js | 13 ++++++++++---
3 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/src/content.js b/src/content.js
index 5d4f223..bc1c9ce 100644
--- a/src/content.js
+++ b/src/content.js
@@ -8,8 +8,9 @@ const getCurrentUser = () => $(".js-menu-target img").attr("alt").slice(1) || ""
const isPrivate = () => $(".label-private").length > 0;
let statsScope = "repo";
-const githubURLBase = window.location.hostname === 'github.com' ? 'api.github.com' : `${window.location.hostname}/api/v3`;
-const githubURL = `https://${githubURLBase}`
+const isEnterprise = window.location.hostname !== 'github.com';
+const githubURLBase = isEnterprise ? `${window.location.hostname}/api/v3` : 'api.github.com';
+const githubURL = `https://${githubURLBase}`;
function getContributor() {
let $contributor = $(".timeline-comment-wrapper .timeline-comment-header-text strong a");
@@ -333,11 +334,15 @@ function update({ contributor, repoPath, currentNum, user }) {
if (storageRes.prs || storageRes.issues) {
updateTextNodes(appendPRText(currentNum, storageRes));
} else {
- getSyncStorage({ "access_token": null })
+ const token = isEnterprise ? "enterprise_access_token" : "access_token";
+
+ getSyncStorage({ [token]: null })
.then((res) => {
+ const access_token = isEnterprise ? res.enterprise_access_token : res.access_token;
+
Promise.all([
- contributorCount({ old: storageRes, user, access_token: res.access_token, type: "pr", contributor, repoPath}),
- contributorCount({ old: storageRes, user, access_token: res.access_token, type: "issue", contributor, repoPath})
+ contributorCount({ old: storageRes, user, access_token, type: "pr", contributor, repoPath}),
+ contributorCount({ old: storageRes, user, access_token, type: "issue", contributor, repoPath})
])
.then(([prInfo, issueInfo]) => {
let repoInfo = Object.assign(prInfo, issueInfo);
diff --git a/src/options.html b/src/options.html
index 8b6d697..839f2b9 100644
--- a/src/options.html
+++ b/src/options.html
@@ -42,6 +42,8 @@ Github's Search API Rate Limit
Access Token
+ Enterprise Access Token
+
diff --git a/src/options.js b/src/options.js
index 43ec1cf..8368312 100644
--- a/src/options.js
+++ b/src/options.js
@@ -2,13 +2,15 @@
document.addEventListener("DOMContentLoaded", () => {
const accessTokenInput = document.getElementById("token-input");
+ const enterpriseAccessTokenInput = document.getElementById("enterprise-token-input");
const oauthLink = document.getElementById("use-oauth");
const clearCacheLink = document.getElementById("clear-cache");
const showPrivateReposInput = document.getElementById("show-private-repos");
- getSyncStorage({ "access_token": null, "_showPrivateRepos": null })
- .then(({ access_token, _showPrivateRepos }) => {
+ getSyncStorage({ "access_token": null, "enterprise_access_token": null, "_showPrivateRepos": null })
+ .then(({ access_token, enterprise_access_token, _showPrivateRepos }) => {
if (access_token) accessTokenInput.value = access_token;
+ if (enterprise_access_token) enterpriseAccessTokenInput.value = enterprise_access_token;
if (_showPrivateRepos) showPrivateReposInput.checked = _showPrivateRepos;
});
@@ -16,14 +18,19 @@ document.addEventListener("DOMContentLoaded", () => {
setSyncStorage({ "access_token": accessTokenInput.value });
});
+ enterpriseAccessTokenInput.addEventListener("change", () => {
+ setSyncStorage({ "enterprise_access_token": enterpriseAccessTokenInput.value });
+ });
+
oauthLink.addEventListener("click", () => {
getTokenFromOauth();
});
clearCacheLink.addEventListener("click", () => {
let temp = accessTokenInput.value;
+ let temp2 = enterpriseAccessTokenInput.value;
chrome.storage.sync.clear(() => {
- setSyncStorage({ "access_token": temp });
+ setSyncStorage({ "access_token": temp, "enterprise_access_token": temp2 });
document.querySelector("#feedback").textContent = "Storage Cleared";
});
});