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

feat: browser extension, custom server support #1220

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
39 changes: 37 additions & 2 deletions packages/webextension-v3/src/action/action.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@
display: none;
}

#default-server-desc {
color: rgba(0, 0, 0, 0.8);
font-size: 12px;
}

#default-server-desc input {
width: initial;
padding: initial;
margin-left: 0;
}

#custom-server-input {
display: none;
}

#message {
display: inline-block;
padding-top: 10px;
Expand Down Expand Up @@ -122,9 +137,29 @@ <h2>Login</h2>
Password<br />
<input id="password" type="password" />
</label>
<br /><br />
<br />

<label id="default-server-desc">
<input id="default-server-checkbox" type="checkbox" checked /> Use
default https://api.recipesage.com/ instance.
</label>
<br />

<div id="custom-server-input">
<br />
<label>
Custom Server API URL<br />
<input id="server" type="url" value="https://api.recipesage.com/" />
</label>
<br />
</div>

<br />
<button id="login-submit">&nbsp;Login&nbsp;</button>&nbsp;
<a href="https://recipesage.com/#/auth/register" target="_blank"
<a
id="register-link"
href="https://recipesage.com/#/auth/register"
target="_blank"
>Create an account</a
>
</div>
Expand Down
120 changes: 90 additions & 30 deletions packages/webextension-v3/src/action/action.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
const API_BASE = "https://api.recipesage.com/";
let api_url;
let base_url;

const getServerUrls = () => {
chrome.storage.local.get(["api_url", "base_url"], (result) => {
api_url = result.api_url || "https://api.recipesage.com/";
base_url = result.base_url || "https://recipesage.com/";
});
};

getServerUrls();

chrome.runtime.onMessage.addListener((request) => {
const clipData = request;
console.log(clipData);

saveClip(clipData);
});

let token;

const login = async () => {
try {
const loginResponse = await fetch(API_BASE + "users/login", {
const loginResponse = await fetch(api_url + "users/login", {
method: "POST",
mode: "cors",
cache: "no-cache",
Expand Down Expand Up @@ -41,8 +49,11 @@ const login = async () => {

const data = await loginResponse.json();
const { token } = data;
// Assumes API URL is Base URL with "api." prepended. User can override in
// extension settings.
base_url = api_url.replace("api.", "");

chrome.storage.local.set({ token }, () => {
chrome.storage.local.set({ token, api_url, base_url }, () => {
chrome.storage.local.get(["seenTutorial"], (result) => {
if (result.seenTutorial) {
document.getElementById("message").innerText =
Expand Down Expand Up @@ -95,11 +106,21 @@ const showLogin = () => {
document.getElementById("start").style.display = "none";
};

const showServerInput = () => {
document.getElementById("custom-server-input").style.display = "block";
document.getElementById("register-link").style.display = "none";
};

const hideServerInput = () => {
document.getElementById("custom-server-input").style.display = "none";
document.getElementById("register-link").style.display = "initial";
};

const createImageFromBlob = async (imageBlob) => {
const formData = new FormData();
formData.append("image", imageBlob);

const imageCreateResponse = await fetch(`${API_BASE}images?token=${token}`, {
const imageCreateResponse = await fetch(`${api_url}images?token=${token}`, {
method: "POST",
body: formData,
});
Expand All @@ -112,6 +133,7 @@ const createImageFromBlob = async (imageBlob) => {
};

const interactiveClip = async () => {
getServerUrls();
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

await chrome.scripting.executeScript({
Expand All @@ -123,6 +145,7 @@ const interactiveClip = async () => {
};

const autoClip = async () => {
getServerUrls();
showLoading();

try {
Expand All @@ -138,7 +161,6 @@ const autoClip = async () => {

const clipWithInject = async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["/inject/clip.js"],
Expand All @@ -153,7 +175,7 @@ const clipWithAPI = async () => {
func: () => document.documentElement.innerHTML,
});

const clipResponse = await fetch(`${API_BASE}clip?token=${token}`, {
const clipResponse = await fetch(`${api_url}clip?token=${token}`, {
method: "POST",
mode: "cors",
cache: "no-cache",
Expand Down Expand Up @@ -188,31 +210,31 @@ const saveClip = async (clipData) => {
}
}

const recipeCreateResponse = await fetch(
`${API_BASE}recipes?token=${token}`,
{
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
...clipData,
imageIds: imageId ? [imageId] : [],
}),
const recipeCreateResponse = await fetch(`${api_url}recipes?token=${token}`, {
method: "POST",
mode: "cors",
cache: "no-cache",
headers: {
"Content-Type": "application/json",
},
);
body: JSON.stringify({
...clipData,
imageIds: imageId ? [imageId] : [],
}),
});

if (!recipeCreateResponse.ok) {
switch (recipeCreateResponse.status) {
case 401:
chrome.storage.local.set({ token: null }, () => {
window.alert(
"Please Login. It looks like you're logged out. Please click the\
chrome.storage.local.set(
{ token: null, api_url: null, base_url: null },
() => {
window.alert(
"Please Login. It looks like you're logged out. Please click the\
RecipeSage icon to login again.",
);
});
);
},
);
break;
default:
window.alert(
Expand All @@ -225,8 +247,7 @@ const saveClip = async (clipData) => {
}

const recipeData = await recipeCreateResponse.json();

const url = `https://recipesage.com/#/recipe/${recipeData.id}`;
const url = `${base_url}#/recipe/${recipeData.id}`;
chrome.tabs.create({
url,
active: true,
Expand All @@ -237,14 +258,53 @@ const saveClip = async (clipData) => {
}, 500);
};

const userDetailsValid = () => {
const username = document.getElementById("email").value;
const password = document.getElementById("password").value;
const server = document.getElementById("server").value;

if (!username || !password) {
document.getElementById("message").innerText =
"Please enter a username and password";
return false;
}

if (!server) {
document.getElementById("message").innerText =
"Please enter a server address";
return false;
}

if (!server.startsWith("https://")) {
document.getElementById("message").innerText =
"Please enter a valid https:// URL";
return false;
}

return true;
};

document.addEventListener("DOMContentLoaded", () => {
document.getElementById("default-server-checkbox").onchange = (event) => {
if (event.target.checked) {
hideServerInput();
} else {
showServerInput();
}
};
document.getElementById("server").onchange = (event) => {
// replace ensures that the api_url ends with a forward slash
api_url = event.target.value.replace(/\/?$/, "/");
};
[...document.getElementsByClassName("logo")].forEach(
(logo) =>
(logo.src = chrome.runtime.getURL(
"./images/recipesage-black-trimmed.png",
)),
);
document.getElementById("login-submit").onclick = login;
document.getElementById("login-submit").onclick = () => {
if (userDetailsValid()) login();
};
document.getElementById("password").onkeydown = (event) => {
if (event.key === "Enter") login();
};
Expand Down
6 changes: 2 additions & 4 deletions packages/webextension-v3/src/inject/clip.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { clipRecipe } from "@julianpoy/recipe-clipper/dist/recipe-clipper.mjs";

chrome.storage.local.get(["token"], async (result) => {
window.RC_ML_CLASSIFY_ENDPOINT =
"https://api.recipesage.com/proxy/ingredient-instruction-classifier?token=" +
result.token;
chrome.storage.local.get(["token", "api_url"], async (result) => {
window.RC_ML_CLASSIFY_ENDPOINT = `${result.api_url}proxy/ingredient-instruction-classifier?token=${result.token}`;

const clip = await clipRecipe().catch(() => {
alert("Error while attempting to automatically clip recipe from page");
Expand Down
37 changes: 19 additions & 18 deletions packages/webextension-v3/src/inject/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ if (window[extensionContainerId]) {

console.log("Loading RecipeSage Browser Extension");

const fetchToken = () => {
const fetchTokenAndUrls = () => {
return new Promise((resolve) => {
chrome.storage.local.get(["token"], (result) => {
resolve(result.token);
chrome.storage.local.get(["token", "api_url", "base_url"], (result) => {
resolve(result);
});
});
};
Expand Down Expand Up @@ -55,10 +55,8 @@ if (window[extensionContainerId]) {
autoSnipPending.innerText = "Grabbing Recipe Content...";
autoSnipPendingContainer.appendChild(autoSnipPending);

autoSnipPromise = fetchToken().then((token) => {
window.RC_ML_CLASSIFY_ENDPOINT =
"https://api.recipesage.com/proxy/ingredient-instruction-classifier?token=" +
token;
autoSnipPromise = fetchTokenAndUrls().then((result) => {
window.RC_ML_CLASSIFY_ENDPOINT = `${result.api_url}proxy/ingredient-instruction-classifier?token=${result.token}`;

return clipRecipe().catch(() => {
alert(
Expand Down Expand Up @@ -424,7 +422,7 @@ if (window[extensionContainerId]) {

let submit = async () => {
try {
const token = await fetchToken();
const result = await fetchTokenAndUrls();

let imageId;
try {
Expand All @@ -435,7 +433,7 @@ if (window[extensionContainerId]) {
formData.append("image", imageBlob);

const imageCreateResponse = await fetch(
`https://api.recipesage.com/images?token=${token}`,
`${result.api_url}images?token=${result.token}`,
{
method: "POST",
body: formData,
Expand All @@ -452,7 +450,7 @@ if (window[extensionContainerId]) {
}

const recipeCreateResponse = await fetch(
`https://api.recipesage.com/recipes?token=${token}`,
`${result.api_url}recipes?token=${result.token}`,
{
method: "POST",
headers: {
Expand All @@ -472,19 +470,22 @@ if (window[extensionContainerId]) {
`Recipe Saved!`,
`Click to open`,
4000,
`https://recipesage.com/#/recipe/${data.id}`,
`${result.base_url}#/recipe/${data.id}`,
);
});
} else {
switch (recipeCreateResponse.status) {
case 401:
chrome.storage.local.set({ token: null }, () => {
displayAlert(
"Please Login",
`It looks like you're logged out. Please click the RecipeSage icon to login again.`,
4000,
);
});
chrome.storage.local.set(
{ token: null, api_url: null, base_url: null },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non-blocking] Do we perhaps want to preserve their custom domain if they have an expired session? It might be a little frustrating if every time a session expires they have to re-type their custom domain

() => {
displayAlert(
"Please Login",
`It looks like you're logged out. Please click the RecipeSage icon to login again.`,
4000,
);
},
);
break;
case 412:
displayAlert(
Expand Down
Loading