Skip to content

Commit 43efe91

Browse files
authored
Update to Chrome Manifest v3 specifications (#92)
1 parent 17c0525 commit 43efe91

File tree

7 files changed

+435
-396
lines changed

7 files changed

+435
-396
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ The chrome extension is used by the ClinGen curation workflow (link) ...
77

88
## Release Notes
99

10+
### v3.0
11+
* This is a combination of merging and modifying v1.16 and v2.0.2 below.
12+
* Major update to the internal code and configuration based on Google chrome extension manifest v3 specifications.
13+
* Removal and replacement of Google API (GAPI) which is no longer supported.
14+
* Internal code refactoring and improvements for more reliable error handling.
15+
1016
### v1.16
1117
* Issue #89 - add new flagging candidate reason resulting from discordance project
1218
* Issue #90 - add new 'Remove Flagged Submission' action

scvc/background.html

Lines changed: 0 additions & 8 deletions
This file was deleted.

scvc/background.js

Lines changed: 95 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,110 @@
1-
const API_KEY = 'AIzaSyADs3YruA9LTaRjc7hS94SQvKbQXJSpMp0';
2-
const DISCOVERY_DOCS = ["https://sheets.googleapis.com/$discovery/rest?version=v4"];
3-
4-
// Create a rule that will show the page action when the conditions are met.
5-
const kMatchRule = {
6-
// Declare the rule conditions.
7-
conditions: [new chrome.declarativeContent.PageStateMatcher({
8-
pageUrl: {hostEquals: 'www.ncbi.nlm.nih.gov',
9-
pathContains: 'variation'}})],
10-
// Shows the page action when the condition is met.
11-
actions: [new chrome.declarativeContent.ShowPageAction()]
12-
}
13-
141
// Register the runtime.onInstalled event listener.
152
chrome.runtime.onInstalled.addListener(function() {
163
// Overrride the rules to replace them with kMatchRule.
17-
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
18-
chrome.declarativeContent.onPageChanged.addRules([kMatchRule]);
4+
chrome.declarativeContent.onPageChanged.removeRules(undefined, () => {
5+
chrome.declarativeContent.onPageChanged.addRules([
6+
// Create a rule that will show the page action when the conditions are met.
7+
{
8+
// Declare the rule conditions.
9+
conditions: [new chrome.declarativeContent.PageStateMatcher({
10+
pageUrl: {
11+
hostEquals: 'www.ncbi.nlm.nih.gov',
12+
pathContains: 'variation'
13+
}
14+
})],
15+
// Shows the page action when the condition is met.
16+
actions: [new chrome.declarativeContent.ShowAction()]
17+
}
18+
]);
1919
});
2020
});
2121

22-
function onGAPILoad() {
23-
gapi.client.init({
24-
apiKey: API_KEY,
25-
discoveryDocs: DISCOVERY_DOCS,
26-
}).then(function () {
27-
console.log('gapi initialized');
28-
}, function(error) {
29-
console.log('error on initialize', error);
30-
alert('error on initialize...'+error);
31-
});
32-
}
22+
const base_url = 'https://sheets.googleapis.com/v4/spreadsheets'
23+
const options = 'valueInputOption=USER_ENTERED&includeValuesInResponse=true'
24+
25+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
3326

34-
// Listen for messages from popup.js
35-
chrome.extension.onMessage.addListener(
36-
function(request, sender, sendResponse) {
27+
console.log('===== BACKGROUND.js event recieved:', message, new Date().getTime());
3728

38-
// verify that the request originated from the chrome-extension popup.html form
39-
if (!sender.url.includes("chrome-extension")) return false;
29+
if (message.from === "popup" && message.subject === "saveAnnotation") {
4030

41-
// Get the users email
42-
chrome.identity.getProfileUserInfo(function(userinfo){
43-
if (!userinfo.email) {
44-
alert("No email captured. Please set your browser profile to sync with your google account to bypass this message in the future.");
45-
}
46-
else {
47-
request.user_email=userinfo.email;
48-
}
49-
});
31+
const data = message.data;
5032

51-
// Get the token
52-
chrome.identity.getAuthToken({interactive: true}, function(token) {
33+
// do a sanity validation and fail if the data is not available
34+
if (!data || !data.scv) {
35+
sendResponse({ success: false, message: "Annotation data was not available or no SCV was selected." });
36+
return;
37+
}
5338

54-
// Set scvc auth token
55-
gapi.auth.setToken({
56-
'access_token': token,
57-
});
39+
// Get the users email
40+
chrome.identity.getProfileUserInfo( (userinfo) =>{
41+
if (!userinfo.email) {
42+
const msg = "No email captured. Please set your browser profile to sync with your google account to save annotations.";
43+
console.log('===== BACKGROUND.js event pre-sendResponse on user email not available.', msg, new Date().getTime());
44+
sendResponse({ success: false, message: msg });
45+
return;
46+
}
47+
else {
48+
data.user_email = userinfo.email;
49+
}
50+
});
5851

59-
let body = {};
60-
let range = "";
52+
try {
53+
chrome.identity.getAuthToken({ interactive: true }, (token) => {
54+
if (chrome.runtime.lastError) {
55+
console.log('===== BACKGROUND.js event pre-sendResponse on lastError: msg:', chrome.runtime.lastError.message, new Date().getTime());
56+
sendResponse({ success: false, message: chrome.runtime.lastError.message });
57+
return;
58+
}
59+
const url = `${base_url}/${data.spreadsheet}/values/${data.scv_range}:append?${options}`;
60+
const body = {
61+
values: [[
62+
data.vcv,
63+
data.name,
64+
data.scv,
65+
data.submitter,
66+
data.interp,
67+
data.action,
68+
data.reason,
69+
data.notes,
70+
new Date(), // Timestamp
71+
data.submitter_id,
72+
data.variation_id,
73+
data.user_email,
74+
data.review_status
75+
]]
76+
};
6177

62-
if (request.scv) {
63-
range = request.scv_range;
64-
body = {values: [[
65-
request.vcv,
66-
request.name,
67-
request.scv,
68-
request.submitter,
69-
request.interp,
70-
request.action,
71-
request.reason,
72-
request.notes,
73-
new Date(), // Timestamp
74-
request.submitter_id,
75-
request.variation_id,
76-
request.user_email,
77-
request.review_status
78-
]]};
79-
}
78+
fetch(url, {
79+
method: 'POST',
80+
headers: {
81+
'Authorization': `Bearer ${token}`,
82+
'Content-Type': 'application/json'
83+
},
84+
body: JSON.stringify(body)
85+
}).then(response => {
86+
if (response.ok) {
87+
return response.json();
88+
} else {
89+
throw new Error(`Server error: ${response.status}`);
90+
}
91+
}).then(results => {
92+
console.log('===== BACKGROUND.js event pre-sendResponse on url fetch success.', new Date().getTime());
93+
sendResponse({ success: true, message: `${results.updates.updatedCells} cells appended.` });
94+
}).catch(error => {
95+
console.log('===== BACKGROUND.js event pre-sendResponse on url fetch failure:', error.message, new Date().getTime());
96+
sendResponse({ success: false, message: error.message });
97+
});
98+
});
99+
} catch (error) {
100+
console.log('===== BACKGROUND.js event pre-sendResponse on error getting auth token', error.message, new Date().getTime());
101+
sendResponse({ success: false, message: error.message });
102+
}
103+
104+
console.log('===== BACKGROUND.js event indicate async response', new Date().getTime());
105+
return true; // Indicate async response
106+
107+
}
108+
});
80109

81-
// Append values to the spreadsheet
82-
gapi.client.sheets.spreadsheets.values.append({
83-
spreadsheetId: request.spreadsheet,
84-
range: range,
85-
valueInputOption: 'USER_ENTERED',
86-
resource: body
87-
}).then((response) => {
88-
// On success
89-
console.log(`${response.result.updates.updatedCells} cells appended.`)
90-
sendResponse({success: true});
91-
}, function(error) {
92-
// On error
93-
console.log('error appending values', error)
94-
alert("Error appending values to google sheet...\n"+JSON.stringify(error));
95-
});
96-
})
97110

98-
// Wait for response
99-
return true;
100-
}
101-
);

scvc/content.js

Lines changed: 67 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,36 @@
1-
//const SPREADSHEET_ID = '1pzuWR409vSmoFX9inmoU6el6vjG0SniB1KrxWLeVpaA';
2-
const SPREADSHEET_ID = '1dUnmBZSnz3aeB948b7pIq0iT7_FuCDvtv6FXaVsNcOo';
1+
const SPREADSHEET_ID = '1dUnmBZSnz3aeB948b7pIq0iT7_FuCDvtv6FXaVsNcOo'; // production
32
const SCV_RANGE = 'SCVs';
4-
const VCV_RANGE = 'VCVs';
53

64
// Inform the background page that
75
// this tab should have a page-action.
86
chrome.runtime.sendMessage({
9-
from: 'content',
10-
subject: 'showPageAction',
7+
from: 'content',
8+
subject: 'showPageAction',
119
});
1210

13-
// Listen for messages from the popup.
14-
chrome.runtime.onMessage.addListener((msg, sender, response) => {
15-
16-
// First, validate the message's structure.
17-
if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
18-
19-
function getMatch(text, re, grp) {
20-
var result;
21-
result = text.match(re);
22-
if (result === null) {
23-
return "";
24-
}
25-
return result[grp];
11+
function getMatch(text, re, grp) {
12+
var result;
13+
result = text.match(re);
14+
if (result === null) {
15+
return "";
2616
}
17+
return result[grp];
18+
}
19+
20+
function extractClinVarData() {
21+
2722
// Collect the necessary data.
2823
var cond_origin_re = /\W*Allele origin:.*?(\w+([\,\s]+\w+)*)/is;
2924
var review_method_re = /(practice guideline|reviewed by expert panel|no assertion provided|no interpretation for the single variant|criteria provided, multiple submitters, no conflicts|criteria provided, single submitter|criteria provided, conflicting interpretations|no assertion criteria provided|no classification provided|Flagged submission).*?Method:.*?([\w\,\s]+)*/is;
3025
var subm_scv_re = /\W*\/clinvar\/submitters\/(\d+)\/".*?>(.+?)<\/a>.*?Accession:.*?(SCV\d+\.\d+).*?First in ClinVar:\W(\w+\s\d+\,\s\d+).*?Last updated:.*?(\w+\s\d+\,\s\d+)/is;
3126
var interp_re = /\W*<div.*?<div.*?(\w+([\s\/\-\,]*\w+)*).*?\(([\w\s\,\-]+)\)/is;
32-
27+
3328
var vcv_accession_re = /Accession:.*?(VCV\d+\.\d+)/is;
3429
var vcv_variation_id_re = /Variation ID:.*?(\d+)/is;
3530

36-
var domInfo = {
31+
var clinvarData = {
3732
spreadsheet: SPREADSHEET_ID,
3833
scv_range: SCV_RANGE,
39-
vcv_range: VCV_RANGE,
4034
vcv: "",
4135
name: "",
4236
variation_id: "",
@@ -46,7 +40,7 @@ chrome.runtime.onMessage.addListener((msg, sender, response) => {
4640
vcv_eval_date: "",
4741
row: []
4842
};
49-
43+
5044
var variantBox = document.evaluate("//div[@id='new-variant-details']//dl", document, null, XPathResult.ANY_TYPE, null );
5145
var variantBoxHTML = variantBox.iterateNext().innerHTML;
5246

@@ -56,57 +50,65 @@ chrome.runtime.onMessage.addListener((msg, sender, response) => {
5650

5751
var vcvClassificationTextNode = vcvClassificationText.iterateNext();
5852
var vcvReviewStatusNode = vcvReviewStatus.iterateNext();
59-
53+
6054
if (!vcvClassificationTextNode) {
61-
// for 2 star and below the vcvs review status and classifiction are found with the following
62-
vcvClassificationText = document.evaluate("//div[@class='germline-section']//div[@class='single-item-value']", document, null, XPathResult.ANY_TYPE, null );
63-
vcvReviewStatus = document.evaluate("//div[@class='germline-section']//div[@class='section-cnt']//span", document, null, XPathResult.ANY_TYPE, null );
64-
vcvClassificationTextNode = vcvClassificationText.iterateNext();
65-
vcvReviewStatusNode = vcvReviewStatus.iterateNext();
55+
// for 2 star and below the vcvs review status and classifiction are found with the following
56+
vcvClassificationText = document.evaluate("//div[@class='germline-section']//div[@class='single-item-value']", document, null, XPathResult.ANY_TYPE, null );
57+
vcvReviewStatus = document.evaluate("//div[@class='germline-section']//div[@class='section-cnt']//span", document, null, XPathResult.ANY_TYPE, null );
58+
vcvClassificationTextNode = vcvClassificationText.iterateNext();
59+
vcvReviewStatusNode = vcvReviewStatus.iterateNext();
6660
}
67-
61+
6862
if (!vcvClassificationTextNode) {
69-
// for vcv with no germline scvs there is no classification and review status
70-
vcvClassificationText = document.evaluate("//div[@class='germline-section']/p[@class='without-classification']", document, null, XPathResult.ANY_TYPE, null );
71-
vcvReviewStatus = document.evaluate("//div[@class='germline-section']/p[@class='without-classification']", document, null, XPathResult.ANY_TYPE, null );
72-
vcvClassificationTextNode = vcvClassificationText.iterateNext();
73-
vcvReviewStatusNode = vcvReviewStatus.iterateNext();
63+
// for vcv with no germline scvs there is no classification and review status
64+
vcvClassificationText = document.evaluate("//div[@class='germline-section']/p[@class='without-classification']", document, null, XPathResult.ANY_TYPE, null );
65+
vcvReviewStatus = document.evaluate("//div[@class='germline-section']/p[@class='without-classification']", document, null, XPathResult.ANY_TYPE, null );
66+
vcvClassificationTextNode = vcvClassificationText.iterateNext();
67+
vcvReviewStatusNode = vcvReviewStatus.iterateNext();
7468
}
75-
76-
domInfo.name = document.querySelectorAll('#variant-details-table div div dl dd p')[0].innerText;
77-
domInfo.vcv = getMatch(variantBoxHTML, vcv_accession_re, 1);
78-
domInfo.variation_id = getMatch(variantBoxHTML, vcv_variation_id_re, 1);
79-
domInfo.vcv_review = vcvReviewStatusNode.textContent.trim();
80-
domInfo.vcv_interp = vcvClassificationTextNode.textContent.trim();
81-
69+
70+
clinvarData.name = document.querySelectorAll('#variant-details-table div div dl dd p')[0].innerText;
71+
clinvarData.vcv = getMatch(variantBoxHTML, vcv_accession_re, 1);
72+
clinvarData.variation_id = getMatch(variantBoxHTML, vcv_variation_id_re, 1);
73+
clinvarData.vcv_review = vcvReviewStatusNode.textContent.trim();
74+
clinvarData.vcv_interp = vcvClassificationTextNode.textContent.trim();
75+
8276
var timelineArray = document.querySelectorAll('table.timeline-table tbody tr td');
83-
domInfo.vcv_most_recent = timelineArray[2].innerHTML;
84-
domInfo.vcv_eval_date = timelineArray[3].innerHTML;
85-
77+
clinvarData.vcv_most_recent = timelineArray[2].innerHTML;
78+
clinvarData.vcv_eval_date = timelineArray[3].innerHTML;
79+
8680
var scvarray = document.querySelectorAll('#new-submission-germline table tbody tr');
8781
scvarray.forEach(myFunction);
88-
82+
8983
function myFunction(value, index, array) {
90-
var interp_match = value.cells[0].innerHTML.match(interp_re);
91-
var review_method_match = value.cells[1].innerHTML.match(review_method_re);
84+
var interp_match = value.cells[0].innerHTML.match(interp_re);
85+
var review_method_match = value.cells[1].innerHTML.match(review_method_re);
9286

93-
var cond_origin_match = value.cells[2].innerHTML.match(cond_origin_re); // alert(value.cells[3].innerHTML);
94-
var subm_scv_match = value.cells[3].innerHTML.match(subm_scv_re);
87+
var cond_origin_match = value.cells[2].innerHTML.match(cond_origin_re); // alert(value.cells[3].innerHTML);
88+
var subm_scv_match = value.cells[3].innerHTML.match(subm_scv_re);
9589

96-
domInfo.row.push({
97-
submitter_id: subm_scv_match[1],
98-
submitter: subm_scv_match[2],
99-
scv: subm_scv_match[3],
100-
subm_date: subm_scv_match[5],
101-
origin: cond_origin_match[1],
102-
review: review_method_match[1],
103-
method: review_method_match[2],
104-
interp: interp_match[1],
105-
eval_date: interp_match[3]
106-
});
90+
clinvarData.row.push({
91+
submitter_id: subm_scv_match[1],
92+
submitter: subm_scv_match[2],
93+
scv: subm_scv_match[3],
94+
subm_date: subm_scv_match[5],
95+
origin: cond_origin_match[1],
96+
review: review_method_match[1],
97+
method: review_method_match[2],
98+
interp: interp_match[1],
99+
eval_date: interp_match[3]
100+
});
107101
}
108-
// Directly respond to the sender (popup),
109-
// through the specified callback.
110-
response(domInfo);
111-
}
112-
});
102+
103+
return clinvarData;
104+
}
105+
106+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
107+
console.log('===== CONTENT.js event recieved: msg:', message, new Date().getTime())
108+
if (message.from === "popup" && message.subject === "initializePopup") {
109+
const data = extractClinVarData();
110+
console.log('===== CONTENT.js event pre-sendResponse: ', message, data, new Date().getTime())
111+
sendResponse(data);
112+
}
113+
return true; // Required for async responses
114+
});

0 commit comments

Comments
 (0)