Use new API methods for saving
This commit is contained in:
@ -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
@ -163,6 +163,7 @@ export const uploadFileRequestResolver: ResolverFn<
|
||||
}
|
||||
}
|
||||
|
||||
console.log("createdPageId", createdPageId)
|
||||
return {
|
||||
id: uploadFileData.id,
|
||||
uploadSignedUrl,
|
||||
|
||||
@ -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/${
|
||||
|
||||
@ -22,5 +22,8 @@
|
||||
"webpack": "^5.11.1",
|
||||
"webpack-cli": "^4.3.1",
|
||||
"webpack-merge": "^5.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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==
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -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"
|
||||
|
||||
Reference in New Issue
Block a user