Duplicate post request sent by Service Worker - web

I just found this issue because I was trying to solve the duplicate post request issue when I am using workbox-background-sync. There is a function of my web app to upload the photos. But every time I did uploaded twice to the database. Here is the code I have:
const bgSyncQueue = new workbox.backgroundSync.Queue(
'photoSubmissions',
{
maxRetentionTime: 48 * 60,//48 hours
callbacks: {
queueDidReplay: function (requests) {
if (requests.length === 0) {
removeAllPhotoSubmissions();
}
else {
for(let request of requests) {
if (request.error === undefined && (request.response && request.response.status === 200)) {
removePhotoSubmission();
}
}
}
}
}
});
workbox.routing.registerRoute(
new RegExp('.*\/Home\/Submit'),
args => {
const promiseChain = fetch(args.event.request.clone())
.catch(err => {
bgSyncQueue.addRequest(args.event.request);
addPhotoSubmission();
changePhoto();
});
event.waitUntil(promiseChain);
},
'POST'
);
It may because the fetch(args.event.request.clone()). If I remove it, then there is no duplication anymore. I am using workbox 3.6.1 .

Finally I found the solution. Below is my code:
const photoQueue = new workbox.backgroundSync.Plugin('photoSubmissions', {
maxRetentionTime: 48 * 60, // Retry for max of 48 Hours
callbacks: {
queueDidReplay: function (requests) {
if (requests.length === 0) {
removeAllPhotoSubmissions();
}
else {
for(let request of requests) {
if (request.error === undefined && (request.response && request.response.status === 200)) {
removePhotoSubmission();
}
}
}
}
}
});
const myPhotoPlugin = {
fetchDidFail: async ({originalRequest, request, error, event}) => {
addPhotoSubmission();
changePhoto();
}
};
workbox.routing.registerRoute(
new RegExp('.*\/Home\/Submit'),
workbox.strategies.networkOnly({
plugins: [
photoQueue,
myPhotoPlugin
]
}),
'POST'
);
I removed fetch. If we still want to controll by ourselves, we need to use respondWith(). I have tested it, it is working. But I would like to use more workbox way to solve the problem. I am using workbox 3.6.3 and I created my own plugin to include a callback function fetchDidFail to update my views. Here are the references I found:
one and two. There are no duplicate posts anymore.

Related

chrome.webRequest.onBeforeRequest didn't work in manifest version 3

In my chrome extension with manifest V2 I was using chrome.webRequest.onBeforeRequest to get all requests of current tab. Here is
const dataSet = {};
chrome.webRequest.onBeforeRequest.addListener(function (details) {
if (details && details.url && details.type == "image") {
if (!dataSet[tabId]) {
dataSet[tabId] = new Set([]);
}
const currentSet = dataSet[tabId];
currentSet.add(details.url);
}
}, {
urls: ["<all_urls>"]
});
I'm trying same code in manifest version 3 but event didn't triggers. Also I've tried this workaround but it still didn't works.
chrome.webNavigation.onBeforeNavigate.addListener(function(){
// this event is not being triggered
chrome.webRequest.onBeforeRequest.addListener(function(details){
},{urls: ["<all_urls>"],types: ["main_frame"]});
},{
url: [{hostContains:"domain"}]
});
Also tried to use webNavigation.onHistoryStateUpdated but still onBeforeRequest didn't triggers
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
console.log('wake me up', details);
chrome.webRequest.onBeforeRequest.addListener(
(details) => {
console.log(details);
},
{
urls: ['<all_urls>'],
},
);
});
Console output of background page

Push notification shows object Object even though i am setting the right value

I am trying to implement push notifications with react and nodejs using service workers.
I am having problem while i am showing notification to the user.
Here is my service worker code:
self.addEventListener('push', async (event) => {
const {
type,
title,
body,
data: { redirectUrl },
} = event.data.json()
if (type === 'NEW_MESSAGE') {
try {
// Get all opened windows that service worker controls.
event.waitUntil(
self.clients.matchAll().then((clients) => {
// Get windows matching the url of the message's coming address.
const filteredClients = clients.filter((client) => client.url.includes(redirectUrl))
// If user's not on the same window as the message's coming address or if it window exists but it's, hidden send notification.
if (
filteredClients.length === 0 ||
(filteredClients.length > 0 &&
filteredClients.every((client) => client.visibilityState === 'hidden'))
) {
self.registration.showNotification({
title,
options: { body },
})
}
}),
)
} catch (error) {
console.error('Error while fetching clients:', error.message)
}
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
console.log(event)
if (event.action === 'NEW_MESSAGE') {
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (clients.openWindow) {
clients
.openWindow(event.notification.data.redirectUrl)
.then((client) => (client ? client.focus() : null))
}
}),
)
}
})
When new notification comes from backend with a type of 'NEW_MESSAGE', i get the right values out of e.data and try to use them on showNotification function but it seems like something is not working out properly because notification looks like this even though event.data equals to this => type = 'NEW_MESSAGE', title: 'New Message', body: , data: { redirectUrl: }
Here is how notification looks:
Thanks for your help in advance.
The problem was i assigned parameters in the wrong way.
It should've been like this:
self.registration.showNotification(title, { body })

Why Hook is called in all update services methods

I'm create a hook file with the following information, which is Hooks.js
Hooks.js is working to authenticate an actions with JWT when need it, I dont need it in all servies calls.
As my understanding the syntax to call a hook was app/use route/hooks and those hooks were only applied to and specific route and not globally.
module.exports = {
errorHandler: (context) => {
if (context.error) {
context.error.stack = null;
return context;
}
},
isValidToken: (context) => {
const token = context.params.headers.authorization;
const payload = Auth.validateToken(token);
console.log(payload);
if(payload !== "Invalid" && payload !== "No Token Provided"){
context.data = payload._id;
}
else {
throw new errors.NotAuthenticated('Authentication Error Token');
}
},
isValidDomain: (context) => {
if (
config.DOMAINS_WHITE_LIST.includes(
context.params.headers.origin || context.params.headers.host
)
) {
return context;
}
throw new errors.NotAuthenticated("Not Authenticated Domain");
},
normalizedId: (context) => {
context.id = context.id || context.params.route.id;
},
normalizedCode: (context) => {
context.id = context.params.route.code;
},
};
Then I create a file for services and routes, like the following:
const Hooks = require("../../Hooks/Hooks");
const userServices = require("./user.services");
module.exports = (app) => {
app
.use("/users", {
find: userServices.find,
create: userServices.createUser,
})
.hooks({
before: {
find: [Hooks.isValidDomain],
create: [Hooks.isValidDomain],
},
});
app
.use("/users/:code/validate", {
update: userServices.validateCode,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.normalizedCode],
},
});
app
.use("/users/personal", {
update: userServices.personalInfo,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.isValidToken],
},
});
};
Why Hooks.isValidToken applies to all my update methods? Even if I'm not calling it?
Please help.
app.hooks registers an application level hook which runs for all services. If you only want it for a specific service and method it needs to be app.service('users').hooks().

How to catch all promises with axios.all?

I am scraping a web page using axios and cheerio:
This web page has many links, while more load while scrolling down(like facebook).
I want to scrape each link while scrolling down until I reach the end.
This is a sample of my code:
cheerio = require('cheerio')
axios = require('axios')
function getLink(id) {
return axios(options).then(function(response) {
// Do stuff...
})
}
function scrollDown() {
axios(scrollOptions).then(function(response) {
$ = cheerio.load(response['data'])
isScrollFinished = ($('.page_more').length == 0)
promises = []
newLinks = $('.link') // Get the new links that were loaded while scrolling
newLinks.each(function() {
promises.push(getLink($(this).attr('id')))
})
axios.all(promises).then(responseArr => {
if(isScrollFinished) {
// Exit script
}
})
if(!isScrollFinished) {
scrollDown()
}
})
}
scrollDown()
The problem with this code is that sometimes it doesn't scrape all the links before I exit.
This is because the last axios.all only waits until all the links of the last scrolled page were scraped.
How do I fix this?
I created the promises array as a static variable and only called axios.all on it when the scrolling reached the end:
cheerio = require('cheerio')
axios = require('axios')
function getLink(id) {
return axios(options).then(function(response) {
// Do stuff...
})
}
function scrollDown() {
if (typeof scrollDown.promises === 'undefined') {
scrollDown.promises = [] // Define static variable if undefined
}
axios(scrollOptions).then(function(response) {
$ = cheerio.load(response['data'])
isScrollFinished = ($('.page_more').length == 0)
newLinks = $('.link') // Get the new links that were loaded while scrolling
newLinks.each(function() {
scrollDown.promises.push(getLink($(this).attr('id')))
})
if(isScrollFinished) {
axios.all(scrollDown.promises).then(responseArr => {
// Exit script
})
}
else {
scrollDown()
}
})
}
scrollDown()
Better solutions will gladly be accepted.

Extensions not returned in GraphQL query results

I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.

Resources