Why doesn't this webworker respond to a posted message? - javascript

I am working on a React project, and I have this code:
Here, I initialize a web-worker and post a message when the Documentation component is loaded.
function Documentation() {
const [result, setResult] = useState(null);
useEffect(() => {
console.log('ran');
const worker = new Worker('../../Workers/SolveWorker.js');
worker.postMessage({ type: 'start', data: [5, 10] });
console.log(worker);
return () => {
worker.terminate();
};
}, []);
...
Inside ../../Workers/SolveWorker.js, I have my worker respond to an event:
/* eslint-disable-next-line no-restricted-globals */
self.onmessage = function (e) {
console.log("Worker: Message received from main script");
};
When I load this component, my message appears to be posted but I never receive a response from the web worker. I've looked at the API but have been unable to figure out what I am doing wrong. Additionally, I get this error: SolveWorker.js:1 Uncaught SyntaxError: Unexpected token '<' (at SolveWorker.js:1:1)
I believe this is somehow relating to my HTML file (when I click this error in chrome dev tools it opens up my HTML), but I’m not really sure in what way.
Any help is appreciated.

When you call const worker = new Worker('../../Workers/SolveWorker.js'); your browser will go try to fetch '../../Workers/SolveWorker.js'. The path is relative to the document root (i.e. where your index.html file is located, sounds like /public in your case) like it would be for a script tag src, not the Javascript file where the worker is created.
Alternatively, you can use a rollup plugin to inline workers in which case your file path would be correct because it would go through the normal node resolution algorithm at build time.

Related

Blazor - Service worker not installing due to integrity check failure

I'm trying to setup PWA for my blazor application. I followed the instructions on: https://learn.microsoft.com/en-us/aspnet/core/blazor/progressive-web-app?view=aspnetcore-6.0&tabs=visual-studio
But when I open the deployed website the following error occurs:
Failed to find a valid digest in the 'integrity' attribute for resource 'domain/manifest.json' with computed SHA-256 integrity 'uDWnAIEnaz9hFx7aEpJJVS1a+QB/W7fMELDfHWSOFkQ='. The resource has been blocked.
Unknown error occurred while trying to verify integrity.
service-worker.js:22
Uncaught (in promise) TypeError: Failed to fetch
at service-worker.js:22:54
at async onInstall (service-worker.js:22:5)
In the source file this happens here:
async function onInstall(event) {
console.info('Service worker: Install');
// Fetch and cache all matching items from the assets manifest
const assetsRequests = self.assetsManifest.assets
.filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
.filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
.map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
I think the error is happening since the entry in assetsRequests has a wrong hash and the resource is blocked. If I remove the file from the service-worker-assets.js, the service worker installs and the PWA can be used. But I think this is not a reliable solution.
This also happens sometimes for the appsettings.json. In the service-worker-assets.js I can find the following entry:
{
"hash": "sha256-+Py0\/ezc+0k1sm\/aruGPrVhS1jOCTfPKMhOSS+bunek=",
"url": "manifest.json"
},
So the hash does not seem to match. Where does the browser take the wrong hash from? How can I fix this so it does match?
Also it seems that the app is caching older files sometimes. Even when I do a "Reset Cache & Hard Reload" in Chrome the service-worker.js file is still an older version. Any idea how to fix this as well, since it might be related?
Edit: I was also checking this solution: https://stackoverflow.com/a/69935118/11385442. But in the mentioned blazor.boot.json I cannot find any reference to the manifest.json or the appsettings.json. Only Dlls are listed. So the problem only seems to relate to files not listed in blazor.boot.json.
Edit2: What I can see on the webserver is that the following files are published:
appsettings.json
appsettings.json.br
appsettings.json.gzip
So it seems like compressed version are added. Also the appsettings.json has a different size than the one in the solution. My guess is that somewhere in the build or release pipeline (Azure) the files are modified. But even when I copy the appsettings.json manually to the webserver the error still occurs. I was following Information provided here: https://learn.microsoft.com/en-us/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-5.0
(Diagnosing integrity problems)
My guess was right. The appsettings.json was modified probably due to the xml transformation in the azure pipeline. My current solution is to exclude integrity validation for such resources as described in the following answer: Error loading appsettings.Production.json due to digest integrity issue
Also I changed the "sw-registrator.js" mentioned in the original posts comments to work correctly, because it didn't load the new files into the cache:
function invokeServiceWorkerUpdateFlow(registration) {
if (confirm("New version available, reload?") == true) {
if (registration.waiting) {
console.info(`Service worker registrator: Post skip_waiting...`);
// let waiting Service Worker know it should became active
registration.waiting.postMessage('SKIP_WAITING')
}
}
}
function checkServiceWorkerUpdate(registration) {
setInterval(() => {
console.info(`Service worker registrator: Checking for update... (scope: ${registration.scope})`);
registration.update();
}, 60 * 1000); // 60000ms -> check each minute
}
// check if the browser supports serviceWorker at all
if ('serviceWorker' in navigator) {
// wait for the page to load
window.addEventListener('load', async () => {
// register the service worker from the file specified
const registration = await navigator.serviceWorker.register('/service-worker.js');
// ensure the case when the updatefound event was missed is also handled
// by re-invoking the prompt when there's a waiting Service Worker
if (registration.waiting) {
invokeServiceWorkerUpdateFlow(registration);
}
// detect Service Worker update available and wait for it to become installed
registration.addEventListener('updatefound', () => {
if (registration.installing) {
// wait until the new Service worker is actually installed (ready to take over)
registration.installing.addEventListener('statechange', () => {
if (registration.waiting) {
// if there's an existing controller (previous Service Worker), show the prompt
if (navigator.serviceWorker.controller) {
invokeServiceWorkerUpdateFlow(registration);
} else {
// otherwise it's the first install, nothing to do
console.log('Service worker registrator: Initialized for the first time.')
}
}
});
}
});
checkServiceWorkerUpdate(registration);
let refreshing = false;
// detect controller change and refresh the page
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.info(`Service worker registrator: Refreshing app... (refreshing: ${refreshing})`);
if (!refreshing) {
window.location.reload();
refreshing = true
}
});
});
}
else
{
console.error(`Service worker registrator: This browser doesn't support service workers.`);
}
Also I had to add this in service-worker.js:
self.addEventListener('message', (event) => {
console.info('Service worker: Message received');
if (event.data === 'SKIP_WAITING') {
// Cause the service worker to update
self.skipWaiting();
}
});
This code was mostly taken from https://whatwebcando.today/articles/handling-service-worker-updates/

Google Tag Manager - custom Templates - injectScript API never seems to run

I have been trying my hands on creating a custom template tag, like so:
Code
// Require the necessary APIs
const logToConsole = require('logToConsole');
const injectScript = require('injectScript');
// construct URL
const url = "https://www.dwin1.com/" + data.merchantId + '.js';
// If the user chose to log debug output, initialize the logging method
const log = data.debug ? logToConsole : (() => {});
log('AWIN: Loading script from ' + url);
// If the script loaded successfully, log a message and signal success
const onSuccess = () => {
log('AWIN: Script loaded successfully.');
data.gtmOnSuccess();
};
// If the script fails to load, log a message and signal failure
const onFailure = () => {
log('AWIN: Script load failed.');
data.gtmOnFailure();
};
injectScript(url, onSuccess, onFailure, url);
Permissions
URL Pattern match: https://www.dwin1.com/
Tests
but when I now try to run a test like so
const mockData = {
merchantId: 1001,
debug: true
};
// Call runCode to run the template's code.
runCode(mockData);
// Verify that the tag finished successfully.
assertApi('gtmOnSuccess').wasCalled();
I get this gnarly error and don't know why:
Hey even I faced the same issue, but as I debugged it was due to AdBlocker which was blocking the script, you need to allow it and check if you have any AdBlocker installed on your machine.
I believe that this gtmOnSuccess() function has to be called in your template code, not in your DOM after injection.
In example, if you add this line of code by the end of your code :
injectScript(url, onSuccess, onFailure, url);
data.gtmOnSuccess();
Your test will be successful
Hope this helps

Uncaught SyntaxError: The requested module does not provide an export named 'fs'

I'm not good with javascript. I'm trying to upload a few files to nft.storage as a folder by modifying this example -> https://github.com/nftstorage/nft.storage/blob/main/packages/client/examples/node.js/storeDirectory.js
Instead of uploading via a form, my files are stored in my PC file system. Below is my js code.
<script type="module">
import { NFTStorage, File } from 'https://cdn.skypack.dev/nft.storage'
import { fs } from 'https://cdn.skypack.dev/fs-'
const endpoint = 'https://api.nft.storage' // the default
const token = 'RDA0NjI0MTIzMTA2Mzgy....' // your API key from https://nft.storage/manage
function log(msg) {
msg = JSON.stringify(msg, null, 2)
document.getElementById('out').innerHTML += `${msg}\n`
}
document.querySelector('#upload-file-ipfs').addEventListener('click', async (e) => {
e.preventDefault()
const storage = new NFTStorage({ endpoint, token })
const cid = await storage.storeDirectory([
new File([await fs.promises.readFile('metadata.json')], 'metadata.json'),
new File([await fs.promises.readFile('test.png')], 'test.png'),
])
console.log({ cid })
const status = await storage.status(cid)
console.log(status)
})
</script>
But I keep getting this error below.
Uncaught SyntaxError: The requested module 'https://cdn.skypack.dev/fs-' does not provide an export named 'fs'
I tried replacing 'https://cdn.skypack.dev/fs-' with 'https://www.skypack.dev/view/fs-extra' and ''https://cdn.skypack.dev/graceful-fs' and it gives me the same error.
If I remove the curly braces around 'fs', I get Uncaught Error: [Package Error] "fs" does not exist. (Imported by "fs-").
Any help is highly appreciated. My application is PHP+ JS, not node.js.
It should be without curly braces, as shown in the docs for fs-, but the second error tells you the next problem: You are trying to use a package in the browser which is designed for node.js. (Browsers don't have a native fs module.) Use it in your backend node.js application, not the frontend.
Since you seem to be trying to use this in a browser, probably you shouldn't use the node.js example that you linked (nft.storage/packages/client/examples/node.js/storeDirectory.js) but rather the browser example: https://github.com/nftstorage/nft.storage/blob/main/packages/client/examples/browser
Remember however that a script in a browser can't just read random files from the user's file system. The user has to upload the file in a form (or give access to a certain directory using the browser file system APIs, but that's experimental).

ReactJS ServiceWorker storing the same code in multiple cache files

I am trying to add a serviceworker to an existing React app with this filesystem layout:
Filesystem
Basically a bit of initialization code is stored in the public folder, and all code of importance is in the src folder. In the serviceWorker.js file, I made an array of filenames to cache and call that array in the 'install' event listener, and if I check DevTools I can see that the filenames are present in the cache: when I preview the data in Chrome DevTools however, I see that the code inside the cached files is all from index.html. In fact, I can add anything I want to the filename array and I will find it in cached storage only to find that it is storing the index.html code. It seems like no matter what file I try to add to the cache, only index.html gets loaded.
ServiceWorker.js:
let CACHE_NAME = "MG-cache-v2";
const urlsToCache = [
'/',
'/index.html',
'/src/App.js',
'/monkey'
];
self.addEventListener('install', function (event) {
//perform install steps
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('Opened MG_Cache');
return cache.addAll(urlsToCache);
}).catch(function (error) {
console.error("Error loading cache files: ", error);
})
);
self.skipWaiting();
});
self.addEventListener('fetch', function (event) {
event.respondWith(caches.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(async function () {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames.filter((cacheName) => {
//Return true if you want to remove this cache,
//but remember that caches are shared across the whole origin
return;
}).map(cacheName => caches.delete(cacheName))
);
})
})
Portion of index.html:
<script>
if ('serviceWorker' in navigator)
{
window.addEventListener('load', function () {
navigator.serviceWorker.register('serviceWorker.js').then(function (registration) {
// Registration was successful
console.log("ServiceWorker registration successful with scope: ", registration.scope);
}, function (err) {
// registration failed :
(console.log('ServiceWorker registration failed: ', err));
});
});
}
</script>
Google Devtools Preview:
All files are the same
I have tried a variety of naming strategies in the filename array but all have ended with the same result. At this point I'm at a complete loss.
EDIT: While this does not solve my problem, I found an answer to another problem that gives a little guidance. It seems like the server never finds the file I request and thus returns index.html. I tried placing the serviceWorker.js file in the src folder and moving the service worker registration to App.js and got an error:
`DOMException: Failed to register a ServiceWorker for scope ('http://localhost:3000/src/') with script ('http://localhost:3000/src/serviceWorker.js'): The script has an unsupported MIME type ('text/html'). `
This suggests that the server somehow doesn't have access to the src folder, only public. Any idea why that may be?
An important piece of information I left out it that I'm using Create-React-App. Because of the enforced layout of the filesystem, the serviceWorker must be placed in the public folder: at the same time, the scope of service workers by default is the folder that they are placed in. According to this answer, changing the scope of the service worker to be a level above the folder that it is in requires adding to the HTTP header response of the service worker (not entirely sure what that means), and doing something like that assumes you have some form of a local server set up. Alas, thus far I have just been using npm start to test my app and pushing onto nsmp to make the site live, thus have negleted to do any form of server implementation myself (I know, not very smart of me).
My hotfix was to create a new temporary app with the npx create-react-app my-app --template cra-template-pwa command, copy all files pertaining to service workers from that app (serviceWorkerRegistration.js, service-worker.js, index.js, potentially setupTests.js), and paste them into my app. Then I could simply follow this tutorial. Now my site works offline.

Can I catch 404s in a service worker?

Basically, I have an online app that uses a htaccess file to silently redirect all requests in a given /folder/ to the same html. Then, to decide what to show the user, the page calls
var page_name = location.href.split('/').pop();
This works well online, but could I use a ServiceWorker to support this folder/file model while the page is offline? Or will I always get the page cannot be found error unless I explicitly cache the URLs?
What you describe can be accomplished using the App Shell model.
Your service worker's exact code might look a little different, and tools like Workbox can automate some of this for you, but a very basic, "vanilla" example of a service worker that accomplishes this is:
self.addEvenListener('install', (event) => {
const cacheShell = async () => {
const cache = await caches.open('my-cache');
await cache.add('/shell.html');
};
event.waitUntil(cacheShell());
});
self.addEventListener('fetch', (event) => {
// If this is a navigation request...
if (event.request.mode === 'navigate') {
// ...respond with the cached shell HTML.
event.respondWith(caches.match('/shell.html'));
return;
}
// Any other caching/response logic can go here.
});
Regardless of what the location.href value is, when this service worker is in control, the App Shell HTML will be used to fulfill all navigation requests.

Categories

Resources