Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp firefox extension #32

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
04249f3
update docs link in broser_action
tanmoysrt Dec 27, 2022
2ec9eb1
upgraded to manifest V3
tanmoysrt Dec 30, 2022
36ab27e
added updated link of google slides embed guide
tanmoysrt Dec 30, 2022
870826e
remove unnecessary commented sample codes
tanmoysrt Dec 30, 2022
3e4321b
as manifest v3 has no support for chrome.extension and it inject code…
tanmoysrt Dec 30, 2022
7edb286
use onreadystatechanged listener in place of setInterval
tanmoysrt Dec 30, 2022
3231e6a
renamed some functions
tanmoysrt Dec 30, 2022
08eb2ee
initialization code rewritten
tanmoysrt Dec 30, 2022
f5f4b2b
added gsi client library locally
tanmoysrt Dec 30, 2022
63b1c19
inject parts of embed tool through inject.js
tanmoysrt Dec 30, 2022
6abce4c
axios added for api calling
tanmoysrt Dec 30, 2022
c8d5060
add circuitverse.org as authorized domains
tanmoysrt Dec 30, 2022
37c28f7
add googleapis.com as authorized domains
tanmoysrt Dec 30, 2022
eeb937b
add slides.googleapis.com as authorized domains
tanmoysrt Dec 30, 2022
b541ea7
presentation loading page uri updated with presentations
tanmoysrt Dec 30, 2022
990b55c
service-worker background script completed
tanmoysrt Dec 30, 2022
3de51b4
working an expected ui update required
tanmoysrt Dec 30, 2022
1b91c20
img folder and required icons for design added
tanmoysrt Dec 31, 2022
80b33b4
new imgs added to web_accessible_resources
tanmoysrt Dec 31, 2022
4bb329d
fetch calls are async , so modified to ensure after html got inserted…
tanmoysrt Dec 31, 2022
1bc35d6
removed loading.gif
tanmoysrt Dec 31, 2022
dce8c12
implementation coomppleted
tanmoysrt Dec 31, 2022
7ad10cf
update popup.html text
tanmoysrt Dec 31, 2022
dda3635
stylesheet update
tanmoysrt Dec 31, 2022
6922ae7
version update
tanmoysrt Dec 31, 2022
0f8437e
porting new chrome extension to firefox compaitable version
tanmoysrt Jan 1, 2023
c562ff3
delete junk files
tanmoysrt Jan 1, 2023
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
Binary file added Chrome Extension/img/google-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Chrome Extension/img/logo_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 45 additions & 22 deletions Chrome Extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,39 +1,62 @@
{
"name": "CircuitVerse",
"version": "0.0.1",
"manifest_version": 2,
"version": "0.0.2",
"manifest_version": 3,
"description": "CircuitVerse Chrome Extension - Embed Live Circuit in Google Slides",
"homepage_url": "https://circuitverse.org",
"icons": {
"16": "icons/icon16.png",
"19": "icons/icon19.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"browser_action": {
"default_icon": "icons/icon32.png",
"default_title": "Embed CircuitVerse Live Circuit",
"default_popup": "views/browser_action.html"
"action": {
"default_icon": {
"16": "icons/icon16.png",
"19": "icons/icon19.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"default_title": "CircuitVerse",
"default_popup": "views/popup.html"
},
"default_locale": "en",
"background": {
"scripts": [
"src/bg/background.js"
],
"persistent": true
},
"permissions": [
"https://docs.google.com/*"
"storage",
"tabs"
],
"host_permissions": [
"https://docs.google.com/*",
"https://circuitverse.org/*",
"https://www.googleapis.com/*",
"https://googleapis.com/*",
"https://slides.googleapis.com/*"
],
"background": {
"service_worker": "src/bg/background.js"
},
"content_scripts": [
{
"matches": [
"https://docs.google.com/*"
"https://docs.google.com/presentation/*"
],
"js": [
"src/inject/inject.js"
],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
{
"resources": [
"views/embedTool.html",
"styles/embedTool.css",
"src/inject/embedTool.js",
"src/inject/gsi.client.js",
"img/google-icon.png",
"img/logo_white.png"
],
"matches": [
"https://docs.google.com/*"
]
}
]
],
"externally_connectable": {
"matches": ["https://docs.google.com/*"]
}
}
308 changes: 296 additions & 12 deletions Chrome Extension/src/bg/background.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,301 @@
// if you checked "fancy-settings" in extensionizr.com, uncomment this lines
// On install, open the CircuitVerse Docs in a new tab
chrome.runtime.onInstalled.addListener(function(object) {
chrome.tabs.create({url: 'https://docs.circuitverse.org/#/chapter2/2cvforeducators?id=embed-circuitverse-simulation-in-google-slides'});
});

// var settings = new Store("settings", {
// "sample_setting": "This is how you use Store.js to remember values"
// });
// Handle messages from the injected Embed tool
chrome.runtime.onMessageExternal.addListener(async function(request, sender, sendResponse) {
//? Handle new successful authentication callback
if (request.message_type === "USER_AUTHENTICATION_SUCCESSFUL") {
var access_token = request.access_token;
var expires_in = request.expires_in - 30; // as the internal process takes some time deduct 30 seconds
var expires_in_timestamp = Date.now() + expires_in * 1000;
var email;

// Fetch user profile
var headers = new Headers();
headers.append("Authorization", "Bearer " + access_token);

// example of using a message handler from the inject scripts
var requestOptions = {
method: 'GET',
headers: headers,
redirect: 'follow'
};

// chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
// chrome.pageAction.show(sender.tab.id);
// sendResponse();
// });
fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", requestOptions)
.then(response => response.json())
.then(async(result) => {
email = result.email;
// Store the access token in the local session storage
// as we dont want to keep temporary tokens permerantly as they have max ttl of 1 hour
var payload_ = {}
payload_[email] = {
access_token: access_token,
expires_in: expires_in_timestamp,
};
await chrome.storage.session.set(payload_)
sendResponse({
"message_type": "USER_AUTHENTICATION_SUCCESSFUL",
"email": email
})
})
.catch(error => {
console.log('error', error);
sendResponse({
"message_type": "USER_AUTHENTICATION_FAILED"
})
});
}
//? Used to check whether the email id is logged in and access_token is still valid
else if(request.message_type === "CHECK_LOGIN_STATUS"){
var email = request.email;
var result = await chrome.storage.session.get(email);
// If no record in storage, user is not logged in
if(result[email] === undefined || result[email] === null){
sendResponse({
"message_type": "USER_NOT_LOGGED_IN",
"email": email
})
}else{
// If record is present, check if access_token is still valid and expires_in is not expired
var access_token = result[email].access_token;
var expires_in = result[email].expires_in;
if(expires_in <= Date.now() || access_token === "" || access_token === null || access_token === undefined){
await chrome.storage.session.remove(email);
sendResponse({
"message_type": "USER_NOT_LOGGED_IN",
"email": email
})
}else{
sendResponse({
"message_type": "USER_LOGGED_IN",
"email": email
})
}
}
}
//? Embed the circuit in slides
else if(request.message_type === "EMBED_CIRCUIT"){
var email = request.email;
var link_of_page = request.link_of_page; // extract page id and presentation id
var circuitverse_link = request.circuitverse_link;

chrome.runtime.onInstalled.addListener(function(object) {
chrome.tabs.create({url: 'https://docs.circuitverse.org/#/embedding_circuits?id=embedding-in-google-slides'});
});
// Parse the presentation id and slide id from the link
var tmp_ = parsePresentationLink(link_of_page);
var link_parse_status = tmp_[0];
var presentation_id = tmp_[1];
var slide_id = tmp_[2];
if(link_parse_status === false){
sendResponse({
"message_type": "INVALID_PRESENTATION_LINK"
})
return;
}else{
// Fetch the access token from the local storage
var result = await chrome.storage.session.get(email);
// If no record in storage, user is not logged in
if(result[email] === undefined || result[email] === null){
sendResponse({
"message_type": "USER_NOT_LOGGED_IN",
"email": email
})
}else{
// If record is present, check if access_token is still valid and expires_in is not expired
var access_token = result[email].access_token;
var expires_in = result[email].expires_in;
if(expires_in <= Date.now() || access_token === "" || access_token === null || access_token === undefined){
chrome.storage.session.remove([email]); // we need not to wait for this to complete
sendResponse({
"message_type": "USER_LOGIN_SESSION_EXPIRED",
"email": email
})
}
// If access_token is valid, embed the circuit in the slide
// Step 1: prepare the preview image link and hyperlink
var circuitverse_project_id = getProjectId(circuitverse_link);
var preview_image_link = await getCircuitPreviewImagePath(circuitverse_project_id);
var circuit_hyperlink = getCircuitEmbedPath(circuitverse_project_id);
// Step 2: fetch revison id of the slide
var revison_id_request = await fetchPresentationRevisionId(presentation_id, slide_id, access_token);
if(revison_id_request[0] === false) {
sendResponse({
"message_type": "CIRCUIT_EMBEDDING_FAILED"
})
return;
}
var presentation_revision_id = revison_id_request[1];
// Step 3: insert image
var insert_image_request = await insertImageInSlide(presentation_id, slide_id, presentation_revision_id, preview_image_link, access_token);
if(insert_image_request[0] === false) {
sendResponse({
"message_type": "CIRCUIT_EMBEDDING_FAILED"
})
return;
}
// Step 4: insert hyperlink
var insert_hyperlink_request = await insertHyperlinkInImage(presentation_id, slide_id, insert_image_request[2], insert_image_request[1],circuit_hyperlink, access_token);
if(insert_hyperlink_request[0] === false) {
sendResponse({
"message_type": "CIRCUIT_EMBEDDING_FAILED"
})
return;
}
// TODO refetch revison id and give one retry
// Send success message
sendResponse({
"message_type": "CIRCUIT_EMBEDDING_SUCCESSFUL"
})
}
}
}
})

// Parse project id from url
function getProjectId(url) {
var re1= /https:\/\/circuitverse\.org\/users\/\d*\/projects\/(.*)/;
var re2 = /"https:\/\/circuitverse\.org\/simulator\/embed\/(.*?)"/;
var re3 = /https:\/\/circuitverse\.org\/simulator\/edit\/(.*)/;
var re4 = /https:\/\/circuitverse\.org\/simulator\/embed\/(.*)/;
var re5 = /https:\/\/circuitverse\.org\/simulator\/(.*)/;

if(re1.test(url)) return url.match(re1)[1];
if(re2.test(url)) return url.match(re2)[1];
if(re3.test(url)) return url.match(re3)[1];
if(re4.test(url)) return url.match(re4)[1];
if(re5.test(url)) return url.match(re5)[1];
return "";
}

// Get embed path of circuit from ID
function getCircuitEmbedPath(id) {
return `https://circuitverse.org/simulator/embed/${id}`;
}

// Get circuit image path from ID
async function getCircuitPreviewImagePath(id) {
var queryUrl = `https://circuitverse.org/api/v1/projects/${id}/image_preview`;
var response = await fetch(queryUrl);
var data = await response.json();
return data.project_preview;
}

// Parse presentation id and slide id from url of page
function parsePresentationLink(url) {
// Check whether `link_of_page` is a valid google slides link and consists the slide_id
var re = /https:\/\/docs\.google\.com\/presentation\/d\/(.*)\/edit#slide=id\.(.*)/;
if(!re.test(url)){
returrn [false, null, null];
}
// Extract the slide_id and presentation_id from the link
var presentation_id = url.match(re)[1];
var slide_id = url.match(re)[2];
return [true, presentation_id, slide_id];
}

// Fetch Slides Revison Id
async function fetchPresentationRevisionId(presentation_id, slide_id, access_token) {
var queryUrl = `https://slides.googleapis.com/v1/presentations/${presentation_id}/pages/${slide_id}`;
var response = await fetch(queryUrl, {
headers: {
Authorization: `Bearer ${access_token}`,
}
});
if(response.status !== 200) return [false, null];
var data = await response.json();
return [true, data.revisionId];
}

// Insert image in slide
async function insertImageInSlide(presentation_id, slide_id, revision_id, img_link, access_token) {
// Generate object id for image
var objectId = generateObjectId();
// Prepare headers
var request_headers = new Headers();
request_headers.append("Authorization", `Bearer ${access_token}`);
// Prepare body
var body = {
"requests": [
{
"createImage": {
"objectId": objectId,
"elementProperties": {
"pageObjectId": slide_id,
"size": {
"width": {
"magnitude": 320,
"unit": "pt"
},
"height": {
"magnitude": 240,
"unit": "pt"
}
}
},
"url": img_link
}
}
],
"writeControl": {
"requiredRevisionId": revision_id
}
};
var request_options = {
method: 'POST',
headers: request_headers,
body: JSON.stringify(body),
redirect: 'follow'
};
// Send request
var request = await fetch(`https://slides.googleapis.com/v1/presentations/${presentation_id}:batchUpdate`, request_options);
if(request.status !== 200) return [false, null, null];
var data = await request.json();
// return [status, image object id, revison id]
return [true, objectId, data.writeControl.requiredRevisionId];
}

// Insert hyperlink to image
async function insertHyperlinkInImage(presentation_id, slide_id, revision_id, object_id, hyperlink, access_token) {
// Prepare headers
var request_headers = new Headers();
request_headers.append("Authorization", `Bearer ${access_token}`);
// body
var body = {
"requests": [
{
"updateImageProperties": {
"objectId": object_id,
"fields": "link",
"imageProperties": {
"link": {
"url": hyperlink
}
}
}
}
],
"writeControl": {
"requiredRevisionId": revision_id
}
}
var request_options = {
method: 'POST',
headers: request_headers,
body: JSON.stringify(body),
redirect: 'follow'
}
// Send request
var request = await fetch(`https://slides.googleapis.com/v1/presentations/${presentation_id}:batchUpdate`, request_options);
if(request.status !== 200) return false;
else return true;
}

// Generate object id
function generateObjectId() {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-:123456789';
var charactersLength = Math.floor(Math.random()*(50-10+1)+10); // random number between 10 and 50
for ( var i = 0; i < charactersLength; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
Loading