Service Worker not working when hosted, but works on localhost - progressive-web-apps

I'm working on a PWA and I'm facing an issue with the service worker and I can't figure out what's wrong.
So when I run the lighthouse audit on localhost, it passes every criteria except for the HTTPS one. You can view it below;
However, when I publish the code to my github pages, and run the same audit there, the service worker is never activated. It gives me the error. The status becomes 'redundant' when I run the audit online.
Link: https://ibr4h1m.github.io/MAD5/index.html
Below I'll show the code, which is the exact same on the website that I've mentioned above.
main.js:
//Simple ServiceWorker
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
};
sw.js
const cacheName = 'tourguide-site';
const appShellFiles = ['index.html',
'help.html',
'destinations.html',
'contact.html',
'js/main.js',
'css/style.css',
'sw.js'
];
self.addEventListener('install', (e) => {
console.log('[Service Worker] Install');
e.waitUntil((async () => {
const cache = await caches.open(cacheName);
console.log('[Service Worker] Caching all: app shell and content');
await cache.addAll(appShellFiles);
})());
});
// Simple Activate since the other one is BS
self.addEventListener('activate', function () {
console.log('SW Activated');
});
self.addEventListener('fetch', (e) => {
e.respondWith((async () => {
const r = await caches.match(e.request);
console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
if (r) { return r; }
const response = await fetch(e.request);
const cache = await caches.open(cacheName);
console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
cache.put(e.request, response.clone());
return response;
})());
});
Online audit:

const appShellFiles = ['index.html',
'help.html',
'destinations.html',
'contact.html',
'js/main.js',
'css/style.css',
'sw.js'
];
Remove the sw.js from your appShellFiles

Related

While testing error responses, the test fails with the expected error (React/Jest/ReactQuery/Axios/MSW)

I am trying to test error states of the following MSW rest endpoint:
import { rest } from 'msw'
export const exceptionHandlers = [
rest.post(config.accountApiUrl + '/login', (req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({ data: { message: 'Mock Error Message' } })
)
})
]
This endpoint is called in a custom hook return function thats using React Query's mutateAsync:
const { mutateAsync } = useMutation(AuthApi.login)
const handleLogin = async (props): Promise<void> => {
await mutateAsync(props, {
onSuccess: async () => {
// this block tests fine
}
onError: async () => {
console.log('!!!')
// it reaches this block, '!!!' is logged to the console,
// but the test still fails with `Request failed with status code 500`
}
})
}
return handleLogin
In a test file:
it('handles network errors', async () => {
mswServer.use(...exceptionHandlers)
const user = userEvent.setup()
const screen = render(<LoginForm />)
const submitButton = screen.getByTestId('Login.Submit')
// Complete form
await user.click(submitButton)
})
It doesnt matter what comes after that, the test always fails with
Request failed with status code 500
at createError (node_modules/axios/lib/core/createError.js:16:15)
at settle (node_modules/axios/lib/core/settle.js:17:12)
at XMLHttpRequestOverride.onloadend (node_modules/axios/lib/adapters/xhr.js:54:7)
at XMLHttpRequestOverride.trigger (node_modules/#mswjs/interceptors/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts:176:17)
at node_modules/#mswjs/interceptors/src/interceptors/XMLHttpRequest/XMLHttpRequestOverride.ts:354:16
But its supposed to fail with status 500. That's the whole point. If I change the handler to return another error, ie ctx.status(404), then the test just fails with that error code.
I've tried wrapping the assertion in a try/catch block but the same thing results. I see examples online of people doing (apparently) exactly this and it works fine, so I'm quite confused what's causing this. All other tests that check success states work as expected.
i've had the same problem.
As far as i could understand, the problem is that in test environment there is no handler for the rejected promise.
https://github.com/TanStack/query/issues/4109

How to solve Vercel 500 Internal Server Error?

I have created a project that uses MongoDB to store user info and Next-Auth to authenticate users. On local host this is all working seamlessly. Previously I had a couple errors with my next-auth config, but that seems to be working fine now on Vercel live site. Once the user logs in they are redirected to "my-project/suggestions". On this page I am using getServerSideProps to identify if there is a valid session token. If so, data is pulled from a local json file.
On the live site, when the user logs in, the page is redirected to "/suggestions", yet I am receiving an 500 Internal Server Error page. On the function logs I am getting this error message:
[GET] /_next/data/KpsnuV9k44lUAhQ-0rK-B/suggestions.json
10:10:57:12
2022-05-05T14:10:59.270Z 5b7a7375-045f-4518-864b-7968c3c9385f ERROR [Error: ENOENT: no such file or directory, open '/var/task/public/data/data.json'] {
errno: -2,
syscall: 'open',
path: '/var/task/public/data/data.json',
page: '/suggestions'
}
RequestId: 5b7a7375-045f-4518-864b-7968c3c9385f Error: Runtime exited with error: exit status 1
Runtime.ExitError
This is my first project using MongoDB and Next-Auth.. not so sure what the issue is in this case. In my .env.local file I only have these two variables:
NEXTAUTH_SECRET="MUNKNATION"
NEXTAUTH_URL=http://localhost:3000
How I am pulling the data on local host:
export const getServerSideProps = async (context) => {
const session = await getSession({ req: context.req });
if (!session) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
} else {
let filePath = path.join(process.cwd(), "public", "data", "data.json");
let jsonData = await fs.readFile(filePath);
const data = JSON.parse(jsonData);
const inProgressStatusData = data.productRequests.filter(
(item) => item.status == "in-progress"
);
const liveStatusData = data.productRequests.filter(
(item) => item.status == "live"
);
const plannedStatusData = data.productRequests.filter(
(item) => item.status == "planned"
);
let filterData = filteredData(data, "suggestion");
let feedbackData = {
suggestions: filterData,
progress: inProgressStatusData,
planned: plannedStatusData,
live: liveStatusData,
};
return {
props: { session, feedbackData },
};
}
};
Folder structure:
A simple solution to this problem would be to, inside of your getServerSideProps, instead of calling readFile use readFileSync as follows:
export const getServerSideProps = async (context) => {
...
const file = readFileSync(
join(process.cwd(), "public", "data", "data.json"),
"utf8"
);
const data = JSON.parse(fileData);
...
I have tested this solution with Vercel and it works correctly, in development and production mode.

Cypress crashes when test that uses gmail-tester library finished it work

I'm was trying to use "gmail-tester" library to verify the account creation message.
https://www.npmjs.com/package/gmail-tester
It seems that I settled up everything as it was supposed to be done. When my test is finished I supposed to get an assertion in cypress such as this
Instead, cypress is awaiting for a message for 30seconds
, then browser crashes and I got this
Does anyone know what would cause the problem?
I have managed to complete all steps mentioned in this tutorial:
https://levz0r.medium.com/how-to-poll-a-gmail-inbox-in-cypress-io-a4286cfdb888
../cypress/plugins.index.js
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* #type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
const path = require("path");
const gmail = require("gmail-tester");
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// ...
on("task", {
"gmail:check": async args => {
const { from, to, subject } = args;
const email = await gmail.check_inbox(
path.resolve(__dirname, "credentials.json"), // credentials.json is inside plugins/ directory.
path.resolve(__dirname, "gmail_token.json"), // gmail_token.json is inside plugins/ directory.
subject,
from,
to,
10, // Poll interval (in seconds)
12 // Maximum poll interval (in seconds). If reached, return null, indicating the completion of the task().
);
return email;
}
});
};
testCase.spec.js
import Navigation from '../../../utils/navigation.spec'
import LoginPage from '../../../pageobject/login/login-page'
describe("New user registration", async function() {
beforeEach(() => {
cy.visit(Navigation.Login)
})
it.only("Reset Form: Email is delievered", function() {
const test_id = new Date().getTime();
const incoming_mailbox = `userautomatedtest+${test_id}#gmail.com`;
// const password = uuidv1().split("-")[0];
const login = new LoginPage();
const username = "Cypress" + test_id;
const password = "111#wZOO";
login.registerButton()
.usernameInput(username)
.emailInput(incoming_mailbox)
.firstNameInput("Name")
.lastNameInput("Surname")
.passwordInput(password)
.repeatPasswordInput(password)
.registerButton()
//assert
cy.contains('Registration succeeded').should('be.visible')
cy.task("gmail:check", {
from: "dev.mailer.no.reply#gmail.com",
to: incoming_mailbox,
subject: "Registration confirmation"
})
.then(email => {
assert.isNotNull(email, `Email was not found`);
});
});
});
btw: in documentation is mentioned that by changing this number we can manipulate awaiting time for checking email. In my case, I'm changing this value and nothing is happening.
This is some problem with the OAuth consent screen, probably access given is not correct, or the GMail API isn't enabled.
Using the most recent version of this package, I had the same issue with the plugins/index.js crashing.
I solved this by adjusting the options-parameter to match the gmail task package function check_inbox.
module.exports = (on, config) => {
on("task", {
"gmail:check": async (args) => {
const { from, to, subject } = args;
const email = await gmail.check_inbox(
path.resolve(__dirname, "credentials.json"),
path.resolve(__dirname, "gmail_token.json"),
{
subject: subject,
from: from,
to: to,
wait_time_sec: 10,
max_wait_time_sec: 30,
}
);
return email;
},
});
};

Lighthouse PWA audit returns a "start_url does not respond with a 200 when offline" error

I'm having an issue with Lighthouse's PWA audit. I'm using a Service Worker sw.js that successfully caches both the offline.html fallback (for when the user has no network connection), and the start.html (which is defined as start_url in the manifest.json and is displayed if the user opens the website from the Homescreen icon).
The issue happens when I use Lighthouse to validate the PWA checklist, which throws this (only) error:
start_url does not respond with a 200 when offline
The start_url did respond, but not via a service worker.
I find this odd, because start.html is properly cached upon the service worker install process. My only guess is that the validator is trying to access start.html in offline mode before the service worker can actually cache it to the browser storage.
So, how can I validate that particular issue in Lighthouse's PWA checklist?
Here's my current code:
manifest.json
{
"name": "My Basic Example",
"short_name": "Example",
"icons": [
{
"src": "https://example.com/static/ico/manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "https://example.com/start.html",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#2196f3",
"theme_color": "#2196f3"
}
core.js
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js', {
scope: '/'
}).then(function(registration) {
}).catch(function(err) {
});
navigator.serviceWorker.ready.then(function(registration) {
});
}
sw.js
//cache container
const CACHE_VERSION = 1;
const CACHE_NAME = 'cache-v' + CACHE_VERSION;
//resources
const URL_OFFLINE = 'offline.html';
const URL_START = 'start.html';
//install
self.addEventListener('install', (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
await Promise.all([
cache.add(new Request(URL_OFFLINE, { cache: 'reload' })),
cache.add(new Request(URL_START, { cache: 'reload' }))
]);
})()
);
//force the waiting service worker to become the active service worker
self.skipWaiting();
});
//activate
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
//enable navigation preload if it is supported.
//https://developers.google.com/web/updates/2017/02/navigation-preload
if('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})()
);
//tell the active service worker to take control of the page immediately
self.clients.claim();
});
//fetch
self.addEventListener('fetch', (event) => {
//we only want to call event.respondWith() if this is a navigation request for an HTML page
if(event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
//first, try to use the navigation preload response if it's supported
const preload_response = await event.preload_response;
if(preload_response) {
return preload_response;
}
//always try the network first
const network_response = await fetch(event.request);
return network_response;
} catch (error) {
//catch is only triggered if an exception is thrown, which is likely due to a network error
const cache = await caches.open(CACHE_NAME);
if(event.request.url.includes(URL_START)) {
return await cache.match(URL_START);
}
return await cache.match(URL_OFFLINE);
}
})());
}
});
Any ideas? Thanks!
#PeteLe, I tried your sample and got following failure:
Navigated to https://basic-pwa-so-1.glitch.me/
The service worker navigation preload request failed with network error: net::ERR_INTERNET_DISCONNECTED.
Looks like something was updated in Lighthouse generating the error message

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.