Service workers "sync" operation is working while its offline? - progressive-web-apps

I have a PWA project where I send the data to server. During this process, if the user is offline then the data is stored in indexedDb and a sync tag is registered. So, then when the user comes online that data can sent to the server.
But In my case the sync event gets executed immediately when the we register a sync event tag, which means the data is tried to be sent to server while its offline, which is not going to work.
I think the sync event supposed to fire while its online only, what could be issue here ?
The service worker's sync event works accordingly when I tried to enable and disable the offline option of chrome devtools, and also works correctly in my android phone.
This is how I register my sync tag
function onFailure() {
var form = document.querySelector("form");
//Register the sync on post form error
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready
.then(function (sw) {
var post = {
datetime1: form.datetime1.value,
datetime: form.datetime.value,
name: form.name.value,
image: form.url.value,
message: form.comment.value
};
writeData('sync-comments', post)
.then(function () {
return sw.sync.register('sync-new-comment');
})
.then(function () {
console.log("[Sync tag registered]");
})
.catch(function (err) {
console.log(err);
});
});
}
}
And this is how the sync event is called
self.addEventListener('sync', function (event) {
console.log("[Service worker] Sync new comment", event);
if (event.tag === 'sync-new-comment') {
event.waitUntil(
readAllData('sync-comments')
.then(function (data) {
setTimeout(() => {
data.forEach(async (dt) => {
const url = "/api/post_data/post_new_comment";
const parameters = {
method: 'POST',
headers: {
'Content-Type': "application/json",
'Accept': 'application/json'
},
body: JSON.stringify({
datetime: dt.datetime,
name: dt.name,
url: dt.image,
comment: dt.message,
datetime1: dt.datetime1,
})
};
fetch(url, parameters)
.then((res) => {
return res.json();
})
.then(response => {
if (response && response.datetimeid) deleteItemFromData('sync-comments', response.datetimeid);
}).catch((error) => {
console.log('[error post message]', error.message);
})
})
}, 5000);
})
);
}
});

you mention
The service worker's sync event works accordingly when I tried to enable and disable the offline option of chrome devtools, and also works correctly in my android phone.
So I'm not sure which case is the one failing.
You are right that the sync will be triggered when the browser thinks the user is online, if the browser detects that the user is online at the time of the sync registration it will trigger the sync:
In true extensible web style, this is a low level feature that gives you the freedom to do what you need. You ask for an event to be fired when the user has connectivity, which is immediate if the user already has connectivity. Then, you listen for that event and do whatever you need to do.
Also, from the workbox documentation
Browsers that support the BackgroundSync API will automatically replay failed requests on your behalf at an interval managed by the browser, likely using exponential backoff between replay attempts.

Related

Can't login by Goggle accounts connect or by Google auth API in Cypress 10x

Current behavior
I've tried to connect to Google account when my tested application redirects to Google accounts connect for let the end-user send emails by the application but I'm not able to do it not by Google Auth API according to your guidelines:
https://docs.cypress.io/guides/end-to-end-testing/google-authentication#Custom-Command-for-Google-Authentication
and not by cy.origin() from the UI.
In the first attempt by the API it's ignore of these authentication and popup the dialog to connect by google account as usually even all the credentials and token are valid and return 200 ok.
In the second attempt by cy.origin() it's keep to load the page after the redirect and always reach to timeout and yell about to increase the timeout even the page seems like it was fully loaded after a few seconds.
I've tried to increase the timeout to 90 seconds and use wait() before and after the redirect and look for some hidden iframes and tried every versa of google domain but nothing help.
it always return errors over there.
all the examples are below.
This is the error when trying to use cy.origin()::
Timed out after waiting 30000ms for your remote page to load on origin(s):
- https://google.com
A cross-origin request for https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&scope=https%3A%2F%2Fmail.google.com&include_granted_scopes=true&state=%7B%22redirectUri%22%3A%22https%3A%2F%2Fmyappurl.com%2Fapp%2Fpipeline%2F9some-token-here-b96b599154ac%3Ftab%3Doverview%22%2C%22clientToken%22%3A%mytokenishere-1234567890%22%7D&prompt=consent&response_type=code&client_id=1234567890-aehhht36f7a01d38bmsvvpjrh915i86v.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fmyredreictedappurl.com%2FusersManagerSrvGoogleLogin was detected.
A command that triggers cross-origin navigation must be immediately followed by a cy.origin() command:
cy.origin('https://google.com', () => {
<commands targeting https://accounts.google.com go here>
})
If the cross-origin request was an intermediary state, you can try increasing the pageLoadTimeout value in Users/myname/repos/myreponame/cypress.config.ts to wait longer.
Browsers will not fire the load event until all stylesheets and scripts are done downloading.
When this load event occurs, Cypress will continue running commands.[Learn more](https://on.cypress.io/origin)
Desired behavior
No response
Test code to reproduce
commands.ts
Cypress.Commands.add('loginByGoogleApi', () => {
cy.log('Logging in to Google')
cy.request({
method: 'POST',
url: 'https://www.googleapis.com/oauth2/v4/token',
body: {
grant_type: 'refresh_token',
client_id: Cypress.env('googleClientId'),
client_secret: Cypress.env('googleClientSecret'),
refresh_token: Cypress.env('googleRefreshToken'),
},
}).then(({ body }) => {
const { access_token, id_token } = body
cy.request({
method: 'GET',
url: 'https://www.googleapis.com/oauth2/v3/userinfo',
headers: { Authorization: `Bearer ${access_token}` },
}).then(({ body }) => {
cy.log(body)
const userItem = {
token: id_token,
user: {
googleId: body.sub,
email: body.email,
givenName: body.given_name,
familyName: body.family_name,
imageUrl: body.picture,
},
}
window.localStorage.setItem('googleCypress', JSON.stringify(userItem))
cy.visit('/')
})
})
})
test-file.cy.ts
it.only('Send email to a user - is shown in the activity', () => {
cy.loginByGoogleApi();
cy.get(loc.sideNavBar.buyersPipeline).should('be.visible').click();
cy.get(loc.pipelineBuyer.nameColumn)
.eq(4)
.should('be.visible')
.click({ force: true });
cy.get(loc.buyerDetails.basicCard).should('be.visible');
cy.get(loc.buyerDetails.timelineSendEmailIcon)
.should('be.visible')
.click();
cy.get('div[role="dialog"]').find('button.MuiButton-root').should('be.visible').click();
})
})
By cy.origin() by the UI:
test-file.cy.ts
it.only('Send email to a user - is shown in the activity', () => {
// cy.loginByGoogleApi();
cy.get(loc.sideNavBar.buyersPipeline).should('be.visible').click();
cy.get(loc.pipelineBuyer.nameColumn)
.eq(4)
.should('be.visible')
.click({ force: true });
cy.get(loc.buyerDetails.basicCard).should('be.visible');
cy.get(loc.buyerDetails.timelineSendEmailIcon)
.should('be.visible')
.click();
cy.get('div[role="dialog"]').find('button.MuiButton-root').should('be.visible').click();
cy.wait(5000);
cy.origin('https://accounts.google.com', () => {
cy.wait(5000);
expect(window.origin).contains('google.com')
cy.get('input[type="email"]', {timeout: 60000}).should('be.visible', {timeout: 60000}).type('111');
})
});
````
### Cypress Version
10.7.0
### Node version
v14.19.1
### Operating System
macOS Montery 12.3.1

Nodemailer. createTestAccount and cypress: generate same email address

I created an E2E to test for signups, using Nodemailer with Ethereal.
When the test runs the first time everything ends smoothly, but when I executed it a second time the test, for some reason, breaks.
While investigating the above issue, I noticed that the createTestAccount returns the same email address (unless cypress is restarted).
Here's the function code for createTestAccount: https://github.com/nodemailer/nodemailer/blob/master/lib/nodemailer.js#L58.
Is createTestAccount using an internal cache?
If yes, is there a way to disable it (besides setting and process.env.ETHEREAL_CACHE to false)?
Based on the current version (6.7.4) of code, it's not possible to disable the cache any other way than setting the env variable ETHEREAL_CACHE to something different than ['true', 'yes', 'y', '1'].
Aka process.env.ETHEREAL_CACHE needs to be false
Keep in mind that this is OS level env variable. Not the ones setup in Cypress.
And the best thing is the great documentation which mentions ETHEREAL_CACHE variable ...
const request = require("request");
async function createEmail (callback) {
request.post({
headers: { "content-type" : "application/json" },
method: "POST",
url: "https://api.nodemailer.com/user",
body: Buffer.from(
JSON.stringify({
requestor: "nodemailer",
version: "6.7.8"
})
)
}, (err, res, body) => {
if(err)
return callback("Failed to handle request!");
const data = JSON.parse(body) || null;
if(typeof(data) !== "object" || typeof(data.status) !== "string")
return callback("Failed to resolve data");
delete data.status;
return callback(false, data);
});
};
createEmail((err, account) => {
if(err)
return console.log(err);
console.log(account);
});

socket.io-client Jest testing inconsistent results

I am writing some end-to-end test cases to test socket connections in my app. I expect receiving socket events after specific rest API requests. For instance, after hitting: /api/v1/[createTag], I expect receiving createTag event to be captured by socket.io-client. The issue is that, it is very inconsistently passing, and sometimes failing, with good rest API requests. The reason to fail is that done() event inside socket.on('createTag' ... is never called, so it gets timeout. On browser, currently all the API endpoints and sockets seem to be working fine. Is there a specific configuration that I might be missing in order to test socket.io-client within Node.js environment and Jest?
Below is my test cases, and thanks a lot in advance:
describe('Socket integration tests: ', () => {
beforeAll(async done => {
await apiInit();
const result = await requests.userSignIn(TEST_MAIL, TEST_PASSWORD);
TEST_USER = result.user;
SESSION = result.user.session;
console.log('Test user authenticated succesfully.');
done();
});
beforeEach(done => {
socket = io(config.socket_host, { forceNew: true })
socket.on('connect', () => {
console.log('Socket connection succesful.');
socket.emit('session', { data: SESSION }, (r) => {
console.log('Socket session successful.');
done();
});
});
})
test('Receiving createTag socket event?', async(done) => {
console.log('API request on createTag');
const response = await Requester.post(...);
console.log('API response on createTag', response);
socket.on('createTag', result => {
console.log('createTag socket event succesful.');
createdTagXid = result.data.xid;
done();
})
});
afterEach(done => {
if(socket.connected) {
console.log('disconnecting.');
socket.disconnect();
} else {
console.log('no connection to break');
}
done();
})
}
Basically, setting event handles after async API calls seems to be the issue. So I should have first set the socket.on( ... and then call rest API.

Keep track of installed pwa instances

We want to keep track of each pwa installation for a user. So we could track notifications, like did every instance of the app user installed (pc or phone, or ff, or chrome) have received the update. Can we know the pwa instance was uninstalled? Can we know web push was delivered to every instance?
In your service worker, fire a http post to your server in the "install" event. You'll need to get your userID from indexdb or similar. Use "fetch" not xmlhttp the service worker.
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function(cache) {
console.log('[ServiceWorker] Caching app shell');
var rc = cache.addAll(filesToCache);
postServerStatus( "installcomplete" );
return rc;
})
);
});
function postServerStatus( strStatus ) {
var strUserID = "getfromindexdb";
fetch('./datasvc.aspx', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
service: strStatus,
userid: strUserID
}),
});
}

Updating/Deleting User Profile with React on Express

I'm working on a web app with React on Express with a Postgresql database, and am working on trying to allow users the ability to delete/update their profiles. I've updated the controller, model, and routes for these changes, but I'm having an issue figuring out where to send the fetch request from the React component. Anytime I try to run a delete or an update I get the following on my terminal:
PUT /api/auth/1 400 3.468 ms - 24
--- undefined /robots.txt
I checked other threads on here and wasn't able to find how I can determine what URL I should point to for these functions. I think once I get that it should work as intended. Below are my auth routes and the functions I have set up, if anybody could suggest how I'd determine what URL to point this to I'd really appreciate it.
// handle profile update/delete
authRouter.route('/dashboard')
.get(usersController.show)
.put(usersController.update)
.delete(usersController.delete)
User Update/Delete functions:
handleUpdateSubmit(e, data, id) {
e.preventDefault()
console.log('clicked')
fetch(`/api/auth/${id}`, {
method: 'PUT',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(res => res.json())
.then(res => {
this.setState({
fireRedirect: true,
redirectPath: '/dashboard'
})
}).catch(err => console.log(err))
}
userDelete(id) {
fetch(`/api/auth/${id}`, {
method: 'DELETE',
}).then(res => res.json())
.then(res => {
this.setState({
fireRedirect: true,
redirectPath: '/'
})
}).catch(err => console.log(err))
}
Please let me know if there's any information that'd be useful for figuring this out and I'll provide it immediately, thanks!
Forgot to follow up on this, the issue was with how my functions were ordered. I moved my authrouter.Route code beneath the login/logout/register functions and it's working as expected.