825 lines
21 KiB
JavaScript
825 lines
21 KiB
JavaScript
/* global
|
||
ACTIONS
|
||
CREATE_ARTICLE_QUERY
|
||
CREATE_ARTICLE_SAVING_REQUEST_QUERY
|
||
ENV_IS_FIREFOX
|
||
ENV_IS_EDGE
|
||
browserApi
|
||
browserActionApi
|
||
browserScriptingApi
|
||
fetch
|
||
XMLHttpRequest
|
||
*/
|
||
|
||
'use strict'
|
||
|
||
import { v4 as uuidv4 } from 'uuid'
|
||
|
||
let authToken = undefined
|
||
const omnivoreURL = process.env.OMNIVORE_URL
|
||
const omnivoreGraphqlURL = process.env.OMNIVORE_GRAPHQL_URL
|
||
|
||
function getCurrentTab() {
|
||
return new Promise((resolve) => {
|
||
browserApi.tabs.query(
|
||
{
|
||
active: true,
|
||
currentWindow: true,
|
||
},
|
||
function (tabs) {
|
||
resolve(tabs[0] || null)
|
||
}
|
||
)
|
||
})
|
||
}
|
||
|
||
function setupConnection(xhr) {
|
||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||
if (authToken) {
|
||
xhr.setRequestHeader('Authorization', authToken)
|
||
}
|
||
}
|
||
|
||
/* other code */
|
||
function uploadFile({ id, uploadSignedUrl }, contentType, contentObjUrl) {
|
||
return fetch(contentObjUrl)
|
||
.then((r) => r.blob())
|
||
.then((blob) => {
|
||
return new Promise((resolve) => {
|
||
const xhr = new XMLHttpRequest()
|
||
xhr.open('PUT', uploadSignedUrl, true)
|
||
xhr.setRequestHeader('Content-Type', contentType)
|
||
|
||
xhr.onerror = () => {
|
||
resolve(undefined)
|
||
}
|
||
xhr.onload = () => {
|
||
// Uploaded.
|
||
resolve({ id })
|
||
}
|
||
xhr.send(blob)
|
||
})
|
||
})
|
||
.catch((err) => {
|
||
console.error('error uploading file', err)
|
||
return undefined
|
||
})
|
||
}
|
||
|
||
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()
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
text: 'Unable to save page',
|
||
type: 'error',
|
||
errorCode: 401,
|
||
url: omnivoreURL,
|
||
},
|
||
})
|
||
}
|
||
}
|
||
|
||
if (
|
||
!data.uploadFileRequest ||
|
||
!data.uploadFileRequest.id ||
|
||
!data.uploadFileRequest.createdPageId ||
|
||
'errorCodes' in data.uploadFileRequest
|
||
) {
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
text: 'Unable to save page',
|
||
type: 'error',
|
||
},
|
||
})
|
||
} else {
|
||
const result = await uploadFile(
|
||
data.uploadFileRequest,
|
||
contentType,
|
||
contentObjUrl
|
||
)
|
||
URL.revokeObjectURL(contentObjUrl)
|
||
|
||
if (!result) {
|
||
return undefined
|
||
}
|
||
|
||
const createdPageId = data.uploadFileRequest.createdPageId
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.UpdateStatus,
|
||
payload: {
|
||
status: 'success',
|
||
target: 'page',
|
||
requestId: createdPageId,
|
||
},
|
||
})
|
||
return resolve(data.uploadFileRequest)
|
||
}
|
||
} else if (xhr.status === 400) {
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
text: 'Unable to save page',
|
||
type: 'error',
|
||
},
|
||
})
|
||
}
|
||
resolve(false)
|
||
}
|
||
}
|
||
|
||
const data = JSON.stringify({
|
||
query: `mutation UploadFileRequest($input: UploadFileRequestInput!) {
|
||
uploadFileRequest(input:$input) {
|
||
... on UploadFileRequestError {
|
||
errorCodes
|
||
}
|
||
... on UploadFileRequestSuccess {
|
||
id
|
||
createdPageId
|
||
uploadSignedUrl
|
||
}
|
||
}
|
||
}`,
|
||
variables: {
|
||
input: {
|
||
url,
|
||
contentType,
|
||
createPageEntry: true,
|
||
},
|
||
},
|
||
})
|
||
|
||
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
|
||
setupConnection(xhr)
|
||
|
||
xhr.send(data)
|
||
})
|
||
}
|
||
|
||
function clearClickCompleteState() {
|
||
getStorageItem('postInstallClickComplete').then(
|
||
(postInstallClickComplete) => {
|
||
if (postInstallClickComplete) {
|
||
removeStorage('postInstallClickComplete')
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
function handleSaveResponse(tab, field, xhr) {
|
||
if (xhr.readyState === 4) {
|
||
if (xhr.status === 200) {
|
||
const { data } = JSON.parse(xhr.response)
|
||
console.log('response data: ', data)
|
||
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.UpdateStatus,
|
||
payload: {
|
||
status: 'success',
|
||
target: 'page',
|
||
link: url ?? omnivoreURL + '/home',
|
||
},
|
||
})
|
||
|
||
return item
|
||
} else if (xhr.status === 400) {
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
text: 'Unable to save page',
|
||
type: 'error',
|
||
},
|
||
})
|
||
return undefined
|
||
}
|
||
}
|
||
}
|
||
|
||
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.ShowToolbar,
|
||
payload: {
|
||
type: 'loading',
|
||
requestId: input.clientRequestId,
|
||
},
|
||
})
|
||
|
||
return new Promise((resolve, reject) => {
|
||
const xhr = new XMLHttpRequest()
|
||
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
|
||
setupConnection(xhr)
|
||
|
||
xhr.onerror = (err) => {
|
||
reject(err)
|
||
}
|
||
|
||
xhr.onload = () => {
|
||
try {
|
||
const res = handleSaveResponse(currentTab, field, xhr)
|
||
if (!res) {
|
||
return reject()
|
||
}
|
||
resolve(res)
|
||
} catch (err) {
|
||
reject(err)
|
||
}
|
||
}
|
||
|
||
const data = JSON.stringify({
|
||
query,
|
||
variables: {
|
||
input,
|
||
},
|
||
})
|
||
|
||
xhr.send(data)
|
||
}).catch((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
|
||
})
|
||
}
|
||
|
||
async function saveArticle(tab) {
|
||
browserApi.tabs.sendMessage(
|
||
tab.id,
|
||
{
|
||
action: ACTIONS.GetContent,
|
||
},
|
||
async (response) => {
|
||
if (!response || typeof response !== 'object') {
|
||
// In the case of an invalid response, we attempt
|
||
// to just save the URL. This can happen in Firefox
|
||
// with PDF URLs
|
||
await saveUrl(tab, tab.url)
|
||
return
|
||
}
|
||
|
||
const requestId = uuidv4()
|
||
var { type } = response
|
||
const { pageInfo, doc, uploadContentObjUrl } = response
|
||
|
||
if (type == 'html' && handleBackendUrl(tab.url)) {
|
||
type = 'url'
|
||
}
|
||
|
||
switch (type) {
|
||
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': {
|
||
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
|
||
}
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
// credit: https://stackoverflow.com/questions/21535233/injecting-multiple-scripts-through-executescript-in-google-chrome
|
||
function executeScripts(tabId, scriptsArray, onCompleteCallback) {
|
||
function createCallback(tabId, injectDetails, innerCallback) {
|
||
return function () {
|
||
browserScriptingApi.executeScript(tabId, injectDetails, innerCallback)
|
||
}
|
||
}
|
||
|
||
// any callback passed by caller is called upon all scripts execute completion
|
||
let callback = onCompleteCallback
|
||
for (let i = scriptsArray.length - 1; i >= 0; --i) {
|
||
callback = createCallback(tabId, { file: scriptsArray[i] }, callback)
|
||
}
|
||
if (callback !== null) {
|
||
callback() // execute outermost function
|
||
}
|
||
}
|
||
|
||
function cleanupTabState(tabId) {
|
||
getStorage().then(function (result) {
|
||
const itemsToRemove = []
|
||
const keys = Object.keys(result)
|
||
const keyPrefix = tabId + '_saveInProgress'
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const key = keys[i]
|
||
if (key.startsWith(keyPrefix)) {
|
||
itemsToRemove.push(key)
|
||
}
|
||
}
|
||
if (itemsToRemove.length === 0) return
|
||
removeStorage(itemsToRemove)
|
||
})
|
||
}
|
||
|
||
/* setup an interval timer and a timeout timer (failsave) to clear interval timer after a timeout */
|
||
function setupTimedInterval(
|
||
timerCallback,
|
||
timeoutCallback,
|
||
callback,
|
||
delay = 1000,
|
||
timeout = 10500
|
||
) {
|
||
const intervalId = setInterval(timerCallback, delay)
|
||
const timeoutId = setTimeout(() => {
|
||
clearInterval(intervalId)
|
||
timeoutCallback()
|
||
}, timeout)
|
||
|
||
if (callback && typeof callback === 'function') {
|
||
callback(intervalId, timeoutId)
|
||
}
|
||
}
|
||
|
||
async function clearPreviousIntervalTimer(tabId) {
|
||
const prevIntervalId = await getStorageItem(tabId + '_saveInProgress')
|
||
if (!prevIntervalId) return
|
||
|
||
clearInterval(prevIntervalId)
|
||
|
||
const intervalTimeoutId = await getStorageItem(
|
||
tabId + '_saveInProgressTimeoutId_' + prevIntervalId
|
||
)
|
||
if (!intervalTimeoutId) return
|
||
|
||
clearTimeout(intervalTimeoutId)
|
||
}
|
||
|
||
function onExtensionClick(tabId) {
|
||
/* clear any previous timers on each click */
|
||
clearPreviousIntervalTimer(tabId)
|
||
|
||
/* Method to check tab loading state prior to save */
|
||
function checkTabLoadingState(onSuccess, onPending) {
|
||
browserApi.tabs.get(tabId, async (tab) => {
|
||
if (tab.status !== 'complete') {
|
||
// show message to user on page yet to complete load
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
type: 'loading',
|
||
text: 'Page loading...',
|
||
},
|
||
})
|
||
if (onPending && typeof onPending === 'function') {
|
||
onPending()
|
||
}
|
||
} else {
|
||
if (onSuccess && typeof onSuccess === 'function') {
|
||
onSuccess()
|
||
}
|
||
await saveArticle(tab)
|
||
try {
|
||
await updateLabelsCache(omnivoreGraphqlURL + 'graphql', tab)
|
||
browserApi.tabs.sendMessage(tab.id, {
|
||
action: ACTIONS.LabelCacheUpdated,
|
||
payload: {},
|
||
})
|
||
} catch (err) {
|
||
console.error('error fetching labels', err, omnivoreGraphqlURL)
|
||
return undefined
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
/* call above method immediately, and if tab in loading state then setup timer */
|
||
checkTabLoadingState(null, () => {
|
||
setupTimedInterval(
|
||
() => {
|
||
/* interval timer to check tab/page loading state and save */
|
||
checkTabLoadingState(() => {
|
||
clearPreviousIntervalTimer(tabId)
|
||
})
|
||
},
|
||
() => {
|
||
/* timeout handling, clear timer and show timeout msg */
|
||
clearPreviousIntervalTimer(tabId)
|
||
|
||
browserApi.tabs.get(tabId, async (tab) => {
|
||
/*
|
||
* post timeout, we proceed to save as some sites (people.com) take a
|
||
* long time to reach complete state and remain in interactive state.
|
||
*/
|
||
await saveArticle(tab)
|
||
})
|
||
},
|
||
(intervalId, timeoutId) => {
|
||
/* Track interval timer and timeout timer in browser storage keyed by tabId */
|
||
const itemsToSet = {}
|
||
itemsToSet[tabId + '_saveInProgress'] = intervalId
|
||
itemsToSet[tabId + '_saveInProgressTimeoutId_' + intervalId] = timeoutId
|
||
setStorage(itemsToSet)
|
||
}
|
||
)
|
||
})
|
||
}
|
||
|
||
/* After installing extension, if user hasn’t logged into Omnivore, then we show the splash popup */
|
||
function checkAuthOnFirstClickPostInstall(tabId) {
|
||
return getStorageItem('postInstallClickComplete').then(
|
||
async (postInstallClickComplete) => {
|
||
if (postInstallClickComplete) return true
|
||
|
||
if (
|
||
typeof browser !== 'undefined' &&
|
||
browser.runtime &&
|
||
browser.runtime.sendNativeMessage
|
||
) {
|
||
const response = await browser.runtime.sendNativeMessage('omnivore', {
|
||
message: ACTIONS.GetAuthToken,
|
||
})
|
||
if (response.authToken) {
|
||
authToken = response.authToken
|
||
}
|
||
}
|
||
|
||
return new Promise((resolve) => {
|
||
const xhr = new XMLHttpRequest()
|
||
xhr.onreadystatechange = function () {
|
||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||
const { data } = JSON.parse(xhr.response)
|
||
if (!data.me) {
|
||
browserApi.tabs.sendMessage(tabId, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
type: 'loading',
|
||
text: 'Loading...',
|
||
},
|
||
})
|
||
browserApi.tabs.sendMessage(tabId, {
|
||
action: ACTIONS.ShowMessage,
|
||
payload: {
|
||
text: '',
|
||
type: 'error',
|
||
errorCode: 401,
|
||
url: omnivoreURL,
|
||
},
|
||
})
|
||
resolve(null)
|
||
} else {
|
||
setStorage({
|
||
postInstallClickComplete: true,
|
||
})
|
||
resolve(true)
|
||
}
|
||
}
|
||
}
|
||
|
||
const query = '{me{id}}'
|
||
const data = JSON.stringify({
|
||
query,
|
||
})
|
||
xhr.open('POST', omnivoreGraphqlURL + 'graphql', true)
|
||
setupConnection(xhr)
|
||
|
||
xhr.send(data)
|
||
})
|
||
}
|
||
)
|
||
}
|
||
|
||
function handleActionClick() {
|
||
executeAction(function (currentTab) {
|
||
onExtensionClick(currentTab.id)
|
||
})
|
||
}
|
||
|
||
function executeAction(action) {
|
||
getCurrentTab().then((currentTab) => {
|
||
browserApi.tabs.sendMessage(
|
||
currentTab.id,
|
||
{
|
||
action: ACTIONS.Ping,
|
||
},
|
||
async function (response) {
|
||
if (response && response.pong) {
|
||
// Content script ready
|
||
const isSignedUp = await checkAuthOnFirstClickPostInstall(
|
||
currentTab.id
|
||
)
|
||
if (isSignedUp) {
|
||
action(currentTab)
|
||
}
|
||
} else {
|
||
const extensionManifest = browserApi.runtime.getManifest()
|
||
const contentScripts = extensionManifest.content_scripts
|
||
|
||
// No listener on the other end, inject content scripts
|
||
const scriptFiles = [...contentScripts[0].js, ...contentScripts[1].js]
|
||
executeScripts(currentTab.id, scriptFiles, async function () {
|
||
const isSignedUp = await checkAuthOnFirstClickPostInstall(
|
||
currentTab.id
|
||
)
|
||
if (isSignedUp) {
|
||
action(currentTab)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
)
|
||
})
|
||
}
|
||
|
||
function getIconPath(active, dark) {
|
||
let iconPath = '/images/toolbar/icon'
|
||
if (ENV_IS_FIREFOX) {
|
||
iconPath += '_firefox'
|
||
} else if (ENV_IS_EDGE) {
|
||
iconPath += '_edge'
|
||
}
|
||
if (!active) {
|
||
iconPath += '_inactive'
|
||
}
|
||
/* we have to evaluate this every time as the onchange is not
|
||
* fired inside background pages, due to https://crbug.com/968651 */
|
||
const useDarkIcon =
|
||
typeof dark === 'boolean'
|
||
? dark
|
||
: window.matchMedia('(prefers-color-scheme: dark)').matches
|
||
|
||
if (useDarkIcon) {
|
||
iconPath += '_dark'
|
||
}
|
||
if (ENV_IS_FIREFOX) {
|
||
return iconPath + '.svg'
|
||
}
|
||
|
||
const iconSizes = ['16', '24', '32', '48']
|
||
if (!ENV_IS_EDGE) {
|
||
iconSizes.push('19', '38')
|
||
}
|
||
const iconPaths = {}
|
||
for (let i = 0; i < iconSizes.length; i++) {
|
||
const iconSize = iconSizes[i]
|
||
iconPaths[iconSize] = iconPath + '-' + iconSize + '.png'
|
||
}
|
||
return iconPaths
|
||
}
|
||
|
||
function updateActionIcon(tabId, active, dark) {
|
||
browserActionApi.setIcon({
|
||
path: getIconPath(active, dark),
|
||
tabId: tabId,
|
||
})
|
||
}
|
||
|
||
function getActionableState(tab) {
|
||
if (tab.status !== 'complete') return false
|
||
|
||
const tabUrl = tab.pendingUrl || tab.url
|
||
if (!tabUrl) return false
|
||
|
||
if (!tabUrl.startsWith('https://') && !tabUrl.startsWith('http://'))
|
||
return false
|
||
|
||
if (
|
||
tabUrl.startsWith('https://omnivore.app/') &&
|
||
tabUrl.startsWith('https://dev.omnivore.app/')
|
||
)
|
||
return false
|
||
|
||
return true
|
||
}
|
||
|
||
function reflectIconState(tab) {
|
||
const tabId = tab && tab.id
|
||
if (!tabId) return
|
||
|
||
const active = getActionableState(tab)
|
||
|
||
updateActionIcon(tabId, active)
|
||
}
|
||
|
||
// function updateLabelsCache(tab) {
|
||
// const query = JSON.stringify({
|
||
// query: `query GetLabels {
|
||
// labels {
|
||
// ... on LabelsSuccess {
|
||
// labels {
|
||
// ...LabelFields
|
||
// }
|
||
// }
|
||
// ... on LabelsError {
|
||
// errorCodes
|
||
// }
|
||
// }
|
||
// }
|
||
// fragment LabelFields on Label {
|
||
// id
|
||
// name
|
||
// color
|
||
// description
|
||
// createdAt
|
||
// }
|
||
// `,
|
||
// })
|
||
// return fetch(omnivoreGraphqlURL + 'graphql', {
|
||
// method: 'POST',
|
||
// redirect: 'follow',
|
||
// credentials: 'include',
|
||
// mode: 'cors',
|
||
// headers: {
|
||
// Accept: 'application/json',
|
||
// 'Content-Type': 'application/json',
|
||
// },
|
||
// body: query,
|
||
// })
|
||
// .then((response) => response.json())
|
||
// .then((data) => {
|
||
// const result = data.data.labels.labels
|
||
// return result
|
||
// })
|
||
// .then((labels) => {
|
||
// setStorage({
|
||
// labels: labels,
|
||
// labelsLastUpdated: new Date().toISOString(),
|
||
// })
|
||
// return labels
|
||
// })
|
||
// .then((labels) => {
|
||
// browserApi.tabs.sendMessage(tab.id, {
|
||
// action: ACTIONS.LabelCacheUpdated,
|
||
// payload: {},
|
||
// })
|
||
// })
|
||
// .catch((err) => {
|
||
// console.error('error fetching labels', err, omnivoreGraphqlURL)
|
||
// return undefined
|
||
// })
|
||
// }
|
||
|
||
function init() {
|
||
/* Extension icon switcher on page/tab load status */
|
||
browserApi.tabs.onActivated.addListener(({ tabId }) => {
|
||
// Due to a chrome bug, chrome.tabs.* may run into an error because onActivated is triggered too fast.
|
||
function checkCurrentTab() {
|
||
browserApi.tabs.get(tabId, function (tab) {
|
||
if (browserApi.runtime.lastError) {
|
||
setTimeout(checkCurrentTab, 150)
|
||
}
|
||
reflectIconState(tab)
|
||
})
|
||
}
|
||
|
||
checkCurrentTab()
|
||
})
|
||
|
||
/* Extension icon switcher on page/tab load status */
|
||
browserApi.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||
/* Not an update while this tab is active so we skip updating icon */
|
||
if (!changeInfo.status || !tab || !tab.active) return
|
||
|
||
reflectIconState(tab)
|
||
})
|
||
|
||
browserApi.tabs.onRemoved.addListener((tabId) => {
|
||
/* cleanup any previous saveInProgress state for the tab */
|
||
cleanupTabState(tabId)
|
||
})
|
||
|
||
browserActionApi.onClicked.addListener(handleActionClick)
|
||
|
||
// forward messages from grab-iframe-content.js script to tabs
|
||
browserApi.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||
if (request.forwardToTab) {
|
||
delete request.forwardToTab
|
||
browserApi.tabs.sendRequest(sender.tab.id, request)
|
||
return
|
||
}
|
||
|
||
if (request.action === ACTIONS.RefreshDarkMode) {
|
||
updateActionIcon(sender.tab.id, request.payload.value)
|
||
}
|
||
|
||
if (request.action === ACTIONS.EditTitle) {
|
||
updatePageTitle(
|
||
omnivoreGraphqlURL + 'graphql',
|
||
request.payload.pageId,
|
||
request.payload.title
|
||
).then(() => {
|
||
browserApi.tabs.sendMessage(sender.tab.id, {
|
||
action: ACTIONS.UpdateStatus,
|
||
payload: {
|
||
target: 'title',
|
||
status: 'success',
|
||
},
|
||
})
|
||
})
|
||
}
|
||
|
||
if (request.action === ACTIONS.SetLabels) {
|
||
setLabels(
|
||
omnivoreGraphqlURL + 'graphql',
|
||
request.payload.pageId,
|
||
request.payload.labelIds
|
||
).then(() => {
|
||
browserApi.tabs.sendMessage(sender.tab.id, {
|
||
action: ACTIONS.UpdateStatus,
|
||
payload: {
|
||
target: 'labels',
|
||
status: 'success',
|
||
},
|
||
})
|
||
})
|
||
}
|
||
})
|
||
|
||
// set initial extension icon
|
||
browserActionApi.setIcon({
|
||
path: getIconPath(true),
|
||
})
|
||
|
||
browserApi.contextMenus.create({
|
||
id: 'save-selection',
|
||
title: 'Save to Omnivore',
|
||
contexts: ['link'],
|
||
onclick: async function (obj) {
|
||
executeAction(async function (currentTab) {
|
||
await saveUrl(currentTab, obj.linkUrl)
|
||
})
|
||
},
|
||
})
|
||
}
|
||
|
||
init()
|