Use new API methods for saving

This commit is contained in:
Jackson Harper
2022-07-16 12:17:58 -07:00
parent 0d2241de06
commit 0bcb8a23c5
9 changed files with 192 additions and 211 deletions

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "Omnivore",
"short_name": "Omnivore",
"version": "0.1.22",
"version": "0.1.24",
"description": "Save articles to your Omnivore library",
"author": "Omnivore Media, Inc",
"default_locale": "en",
@ -80,6 +80,14 @@
},
"default_title": "Omnivore Save Article"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Alt + O"
},
"description": "Save the current tab to Omnivore"
}
},
"web_accessible_resources": [
"views/cta-popup.html"
]

File diff suppressed because one or more lines are too long

View File

@ -163,6 +163,7 @@ export const uploadFileRequestResolver: ResolverFn<
}
}
console.log("createdPageId", createdPageId)
return {
id: uploadFileData.id,
uploadSignedUrl,

View File

@ -23,13 +23,11 @@ export const saveFile = async (
): Promise<SaveResult> => {
console.log('saving file with input', input)
// /* We do not trust the values from client, lookup upload file by querying
// * with filtering on user ID and URL to verify client's uploadFileId is valid.
// */
const uploadFile = await ctx.models.uploadFile.getWhere({
id: input.uploadFileId,
userId: saver.id,
})
if (!uploadFile) {
return {
errorCodes: [SaveErrorCode.Unauthorized],
@ -45,64 +43,6 @@ export const saveFile = async (
return ctx.models.uploadFile.setFileUploadComplete(input.uploadFileId, tx)
})
// if (!uploadFileData || !uploadFileData.id || !uploadFileData.fileName) {
// console.log('error completing upload file request', input)
// return {
// errorCodes: [SaveErrorCode.Unknown],
// }
// }
// // const uploadFileUrlOverride = await makeStorageFilePublic(
// // uploadFileData.id,
// // uploadFileData.fileName
// // )
// const matchedUserArticleRecord = await getPageByParam({
// userId: saver.id,
// url: uploadFileData.url,
// state: ArticleSavingRequestStatus.Succeeded,
// })
// if (matchedUserArticleRecord) {
// await updatePage(
// matchedUserArticleRecord.id,
// {
// savedAt: new Date(),
// archivedAt: null,
// },
// ctx
// )
// input.clientRequestId = matchedUserArticleRecord.id
// } else {
// const pageId = await createPage(
// {
// url: uploadFile.url,
// title: uploadFile.fileName,
// hash: uploadFileDetails.md5Hash,
// content: '',
// pageType: PageType.File,
// uploadFileId: input.uploadFileId,
// slug: generateSlug(uploadFile.fileName),
// userId: saver.id,
// id: input.clientRequestId,
// createdAt: new Date(),
// savedAt: new Date(),
// readingProgressPercent: 0,
// readingProgressAnchorIndex: 0,
// state: ArticleSavingRequestStatus.Succeeded,
// },
// ctx
// )
// if (!pageId) {
// console.log('error creating page in elastic', input)
// return {
// errorCodes: [SaveErrorCode.Unknown],
// }
// }
// input.clientRequestId = pageId
// }
return {
clientRequestId: input.clientRequestId,
url: `${homePageURL()}/${saver.profile.username}/links/${

View File

@ -22,5 +22,8 @@
"webpack": "^5.11.1",
"webpack-cli": "^4.3.1",
"webpack-merge": "^5.7.3"
},
"dependencies": {
"uuid": "^8.3.2"
}
}

View File

@ -13,6 +13,8 @@
'use strict';
import { v4 as uuidv4 } from 'uuid';
let authToken = undefined;
const omnivoreURL = process.env.OMNIVORE_URL;
const omnivoreGraphqlURL = process.env.OMNIVORE_GRAPHQL_URL;
@ -76,7 +78,7 @@ function uploadFile ({ id, uploadSignedUrl }, contentType, contentObjUrl) {
xhr.setRequestHeader('Content-Type', contentType);
xhr.onerror = () => {
resolve(null);
resolve(undefined);
};
xhr.onload = () => {
// Uploaded.
@ -91,14 +93,13 @@ function uploadFile ({ id, uploadSignedUrl }, contentType, contentObjUrl) {
});
}
function savePdfFile (tab, url, contentType, contentObjUrl) {
function savePdfFile(tab, url, contentType, contentObjUrl) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = async function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const { data } = JSON.parse(xhr.response);
if ('errorCodes' in data.uploadFileRequest) {
if (data.uploadFileRequest.errorCodes[0] === 'UNAUTHORIZED') {
clearClickCompleteState();
@ -114,7 +115,7 @@ function savePdfFile (tab, url, contentType, contentObjUrl) {
}
}
if (!data.uploadFileRequest || !data.uploadFileRequest.id || 'errorCodes' in data.uploadFileRequest) {
if (!data.uploadFileRequest || !data.uploadFileRequest.id || !data.uploadFileRequest.createdPageId || 'errorCodes' in data.uploadFileRequest) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
@ -123,9 +124,26 @@ function savePdfFile (tab, url, contentType, contentObjUrl) {
}
});
} else {
const uploadFileId = await uploadFile(data.uploadFileRequest, contentType, contentObjUrl);
const result = await uploadFile(data.uploadFileRequest, contentType, contentObjUrl);
URL.revokeObjectURL(contentObjUrl);
return resolve(uploadFileId);
if (!result) {
return undefined
}
const createdPageId = data.uploadFileRequest.createdPageId
const url = omnivoreURL + '/article/sr/' + createdPageId
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Saved to Omnivore',
link: url,
linkText: 'Read Now',
type: 'success'
}
})
return resolve(data.uploadFileRequest);
}
} else if (xhr.status === 400) {
browserApi.tabs.sendMessage(tab.id, {
@ -148,6 +166,7 @@ function savePdfFile (tab, url, contentType, contentObjUrl) {
}
... on UploadFileRequestSuccess {
id
createdPageId
uploadSignedUrl
}
}
@ -155,7 +174,8 @@ function savePdfFile (tab, url, contentType, contentObjUrl) {
variables: {
input: {
url,
contentType
contentType,
createPageEntry: true,
}
}
});
@ -175,65 +195,47 @@ function clearClickCompleteState () {
});
}
function handleSaveResponse (tab, xhr) {
function handleSaveResponse(tab, field, xhr) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const { data } = JSON.parse(xhr.response);
if ('createArticle' in data) {
if ('errorCodes' in data.createArticle) {
const messagePayload = {
text: descriptions[data.createArticle.errorCodes[0]] || 'Unable to save page',
type: 'error'
};
if (data.createArticle.errorCodes[0] === 'UNAUTHORIZED') {
messagePayload.errorCode = 401;
messagePayload.url = omnivoreURL;
clearClickCompleteState();
}
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: messagePayload
});
} else {
const article = data.createArticle.createdArticle;
const user = data.createArticle.user;
const link = omnivoreURL + (article.hasContent ? (`/${user.profile.username}/` + article.slug) : '/home');
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Saved to Omnivore',
link: link,
linkText: 'View',
type: 'success'
}
});
}
} else {
if ('errorCodes' in data.createArticleSavingRequest) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: descriptions[data.createArticleSavingRequest.errorCodes[0]] || 'Unable to save page',
type: 'error'
}
});
} else {
const articleSavingRequest = data.createArticleSavingRequest.articleSavingRequest;
const link = omnivoreURL + '/article/sr/' + articleSavingRequest.id;
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Saved to Omnivore',
link: link,
linkText: 'View',
type: 'success'
}
});
}
const item = data[field]
if (!item) {
return undefined
}
if ('errorCodes' in item) {
const messagePayload = {
text: descriptions[data.createArticle.errorCodes[0]] || 'Unable to save page',
type: 'error'
}
if (item.errorCodes[0] === 'UNAUTHORIZED') {
messagePayload.errorCode = 401
messagePayload.url = omnivoreURL
clearClickCompleteState()
}
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: messagePayload
})
return undefined
}
const url = item['url']
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Saved to Omnivore',
link: url ?? omnivoreURL + '/home',
linkText: 'Read Now',
type: 'success'
}
})
return item
} else if (xhr.status === 400) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
@ -241,12 +243,22 @@ function handleSaveResponse (tab, xhr) {
text: 'Unable to save page',
type: 'error'
}
});
})
return undefined
}
}
}
async function saveUrl (currentTab, url) {
async function saveUrl(currentTab, url) {
const requestId = uuidv4()
await saveApiRequest(currentTab, SAVE_URL_QUERY, 'saveUrl', {
source: 'extension',
clientRequestId: requestId,
url: encodeURI(url),
})
}
async function saveApiRequest(currentTab, query, field, input) {
browserApi.tabs.sendMessage(currentTab.id, {
action: ACTIONS.ShowMessage,
payload: {
@ -256,61 +268,47 @@ async function saveUrl (currentTab, url) {
});
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true);
setupConnection(xhr);
xhr.onerror = (err) => {
resolve(null);
};
xhr.onload = (res) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
setupConnection(xhr)
xhr.onerror = (err) => { reject(err) }
xhr.onload = () => {
try {
handleSaveResponse(currentTab, xhr)
const res = handleSaveResponse(currentTab, field, xhr)
if (!res) {
return reject()
}
resolve(res);
} catch (err) {
console.log('response error', err)
reject(err)
}
resolve({});
};
}
const data = JSON.stringify({
query: CREATE_ARTICLE_SAVING_REQUEST_QUERY,
query,
variables: {
input: {
url
}
input
}
});
})
xhr.send(data);
})
.catch((err) => {
console.error('error saving url', err)
console.log('error saving page', err)
browserApi.tabs.sendMessage(currentTab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Unable to save page',
type: 'error',
}
});
return undefined
});
}
function saveArticle (tab) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
type: 'loading',
text: 'Saving...'
}
});
const xhr = new XMLHttpRequest();
const descriptions = {
BAD_DATA: 'Unable to save page',
NOT_ALLOWED_TO_PARSE: 'Not allowed to parse this article',
UNAUTHORIZED: 'Please login to Omnivore to authorize this action',
UNABLE_TO_FETCH: 'Unable to fetch page',
PAYLOAD_TOO_LARGE: 'This article is too large'
};
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
handleSaveResponse(tab, xhr)
}
};
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.GetContent
}, async (response) => {
@ -319,56 +317,42 @@ function saveArticle (tab) {
return;
}
const requestId = uuidv4()
const { type, pageInfo, doc, uploadContentObjUrl } = response;
let uploadResult = null;
switch(type) {
case 'pdf': {
// For PDFs, we first upload the PDF file before passing the upload file ID in createArticle
uploadResult = await savePdfFile(tab, encodeURI(tab.url), pageInfo.contentType, uploadContentObjUrl);
if (!uploadResult || !uploadResult.id) {
browserApi.tabs.sendMessage(tab.id, {
action: ACTIONS.ShowMessage,
payload: {
text: 'Unable to save page',
type: 'error'
}
});
return;
}
case 'html': {
await saveApiRequest(tab, SAVE_PAGE_QUERY, 'savePage', {
source: 'extension',
clientRequestId: requestId,
originalContent: doc,
title: pageInfo.title,
url: encodeURI(tab.url),
})
break
}
case 'url': {
// We don't have to special case URL, it will fall through
// and be handled when isContentAvailable returns false
await saveApiRequest(tab, SAVE_URL_QUERY, 'saveUrl', {
source: 'extension',
clientRequestId: requestId,
url: encodeURI(tab.url),
})
break
}
case 'pdf': {
const uploadResult = await savePdfFile(tab, encodeURI(tab.url), pageInfo.contentType, uploadContentObjUrl);
if (!uploadResult || !uploadResult.id) {
// If the upload failed for any reason, try to save the PDF URL instead
await saveApiRequest(tab, SAVE_URL_QUERY, 'saveUrl', {
source: 'extension',
clientRequestId: requestId,
url: encodeURI(tab.url),
})
return;
}
break
}
}
const isContentAvailable = (doc && doc.length) || uploadResult;
const query = isContentAvailable ? CREATE_ARTICLE_QUERY : CREATE_ARTICLE_SAVING_REQUEST_QUERY;
const input = {
url: encodeURI(tab.url)
};
if (isContentAvailable) {
input.preparedDocument = {
document: doc,
pageInfo
};
input.uploadFileId = (uploadResult && uploadResult.id) || null;
}
const data = JSON.stringify({
query,
variables: {
input
}
});
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true);
setupConnection(xhr);
xhr.send(data);
});
}
@ -700,7 +684,7 @@ function init () {
});
browserApi.contextMenus.create({
id: "log-selection",
id: "save-selection",
title: "Save to Omnivore",
contexts: ["link"],
onclick: async function(obj) {

View File

@ -30,6 +30,39 @@ window.DONT_REMOVE_ELEMENTS = [
'title'
];
window.SAVE_URL_QUERY = `mutation SaveUrl ($input: SaveUrlInput!) {
saveUrl(input:$input){
... on SaveSuccess {
url
}
... on SaveError {
errorCodes
}
}
}`
window.SAVE_FILE_QUERY = `mutation SaveFile ($input: SaveFileInput!) {
saveFile(input:$input){
... on SaveSuccess {
url
}
... on SaveError {
errorCodes
}
}
}`
window.SAVE_PAGE_QUERY = `mutation SavePage ($input: SavePageInput!) {
savePage(input:$input){
... on SaveSuccess {
url
}
... on SaveError {
errorCodes
}
}
}`
window.CREATE_ARTICLE_QUERY = `mutation CreateArticle ($input: CreateArticleInput!){
createArticle(input:$input){
... on CreateArticleSuccess{

View File

@ -4655,7 +4655,7 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.3.0:
uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

View File

@ -7678,6 +7678,13 @@
dependencies:
"@types/node" "*"
"@types/graphql-fields@^1.3.4":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@types/graphql-fields/-/graphql-fields-1.3.4.tgz#868ffe444ba8027ea1eccb0909f9c331d1bd620a"
integrity sha512-McLJaAaqY7lk9d9y7E61iQrj0AwcEjSb8uHlPh7KgYV+XX1MSLlSt/alhd5k2BPRE8gy/f4lnkLGb5ke3iG66Q==
dependencies:
graphql "^15.3.0"
"@types/hast@^2.0.0":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc"
@ -14649,6 +14656,11 @@ graphql-config@^4.1.0:
minimatch "3.0.4"
string-env-interpolation "1.0.1"
graphql-fields@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/graphql-fields/-/graphql-fields-2.0.3.tgz#5e68dff7afbb202be4f4f40623e983b22c96ab8f"
integrity sha512-x3VE5lUcR4XCOxPIqaO4CE+bTK8u6gVouOdpQX9+EKHr+scqtK5Pp/l8nIGqIpN1TUlkKE6jDCCycm/WtLRAwA==
graphql-middleware@^6.0.10:
version "6.1.4"
resolved "https://registry.yarnpkg.com/graphql-middleware/-/graphql-middleware-6.1.4.tgz#1b4dd66195477046282acc8937cb5fca32b6bfd5"