Closed Ky6uk closed 3 years ago
Thanks for reporting this. I released a fix:
npm i vue-qrcode-reader@2.3.12-alpha.2
This certainly won't resolve the issue completely since the QrcodeStream
also spawns a webworker which in turn loads a script from a public CDN:
Can you please check if the webrtc-adapter
related problem is fixed anyway?
@gruhn Yep, webrtc problem has been resolved. Now I see only impossibility to load jsqr
because of the same reason above. Strict CSP rules don't allow us to do that. Can you also add a bundled version to make it happen to work on local environments also?
I tried to do that in the past but it's not trivial to get the configuration right when webworkers are involved. I fear it'll take a while before I find the time to do that properly. A workaround might be to:
importScripts
part so jsQR.min.js
is loaded from your static assetsimport Worker from "./jsqr-copy.js"
and pass that via the worker prop, i.e.: <qrcode-stream :worker="Worker"></qrcode-stream>
Please check if this works for you.
What about to add jsqr
dependency to this project and bundle worker module with this local jsqr
package. Users of vue-qrcode-reader
can decide which worker will they use, local or remote one.
As I said this is not trivial.
You can't just import dependencies in web workers. You can importScripts
as we are doing right now. Or bundle all dependencies together with the worker source code. For that matter you probably need a dedicated webpack loader like worker-loader and this should ideally be incorporated into the Vue CLI boilerplate config. You probably need to do that with webpack chaining.
If you manage to implement this or can find a better solution, please open a pull request.
Hi, is there any solution for this? I am using latest V2.3.13 but I need it to work offline which it currently doesn't. Does anybody know an alternative which does work offline?
Thanks
I tried to do that in the past but it's not trivial to get the configuration right when webworkers are involved. I fear it'll take a while before I find the time to do that properly. A workaround might be to:
- put jsQR.min.js together with your static assets
- copy src/worker/jsqr.js into your project
- modify
importScripts
part sojsQR.min.js
is loaded from your static assetsimport Worker from "./jsqr-copy.js"
and pass that via the worker prop, i.e.:<qrcode-stream :worker="Worker"></qrcode-stream>
Please check if this works for you.
You could try this workaround.
I tried to do that in the past but it's not trivial to get the configuration right when webworkers are involved. I fear it'll take a while before I find the time to do that properly. A workaround might be to:
- put jsQR.min.js together with your static assets
- copy src/worker/jsqr.js into your project
- modify
importScripts
part sojsQR.min.js
is loaded from your static assetsimport Worker from "./jsqr-copy.js"
and pass that via the worker prop, i.e.:<qrcode-stream :worker="Worker"></qrcode-stream>
Please check if this works for you.
You could try this workaround.
I tried that but failed at step 3... importScripts from local (relative url) assets in inline worker doesn't seem as easy as it sounds like...
Make sure the file is in a directory for static assets, i.e that is directly served by your web server/dev server. A directory where you usually put font files, images and so on that should not be processed by webpack. If you are using Vue CLI that would be the public
folder. importScripts
does NOT work like import
. It works like <script src="..."></script>
.
Thanks for your answer. Yes, jsQR.min.js is in the static directory for static assets. However, importScripts does not accept relative urls (like './jsQR.min.js'). It throws an error "invalid url". It only accepts absolute urls like 'http://localhost/jsQR.min.js'. This way it works, but it works only on localhost of course...
I see. Ok, that's unfortunate but can you set the origin dynamically with self.location.origin ? So something like this:
importScripts(self.location.origin + '/static/jsQR.min.js')
Thanks a lot. That seems to work
I tried to do that in the past but it's not trivial to get the configuration right when webworkers are involved. I fear it'll take a while before I find the time to do that properly. A workaround might be to:
- put jsQR.min.js together with your static assets
- copy src/worker/jsqr.js into your project
- modify
importScripts
part sojsQR.min.js
is loaded from your static assetsimport Worker from "./jsqr-copy.js"
and pass that via the worker prop, i.e.:<qrcode-stream :worker="Worker"></qrcode-stream>
Please check if this works for you.
@gruhn I'm getting stuck on step 4. I'm using Nuxt and I'm not sure where to put the import or how to pass it in via a prop. Also, it seems like "Worker" isn't defined in worker/jsqr.js
Here's my page file:
<template>
<div>
<h1>Test</h1>
<qrcode-stream :worker="Worker" />
</div>
</template>
<script>
import { QrcodeStream } from 'vue-qrcode-reader'
import { Worker } from '@/assets/js/jsqr.js'
export default {
components: { QrcodeStream },
head: {
title: 'Activate posters',
},
}
</script>
And assets/js/jsqr.js (the file copied from step 2):
const inlineWorker = (func) => {
const functionBody = func
.toString()
.trim()
.match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1]
return new Worker(
URL.createObjectURL(new Blob([functionBody], { type: 'text/javascript' }))
)
}
export default () => {
return inlineWorker(function () {
self.importScripts(self.location.origin + '/jsQR.min.js')
self.addEventListener('message', function (event) {
const imageData = event.data
/* eslint-disable no-undef */
const result = jsQR(imageData.data, imageData.width, imageData.height)
/* eslint-enable */
let content = null
let location = null
if (result !== null) {
content = result.data
location = result.location
}
const message = { content, location, imageData }
self.postMessage(message, [imageData.data.buffer])
})
})
}
Thank you for your time.
Nevermind--I think I got it working. Sorry for pinging you, I didn't realize that there was documentation about the worker prop.
I think my problem was putting Worker
inside of { }
in the import statement. Here's what I used to get it working:
<template>
<div>
<h1>Test</h1>
<qrcode-stream :worker="Worker" />
</div>
</template>
<script>
import { QrcodeStream } from 'vue-qrcode-reader'
import Worker from '@/assets/js/jsqr.js'
export default {
components: { QrcodeStream },
data() {
return {
Worker,
}
},
head: {
title: 'Activate posters',
},
}
</script>
Nevermind--I think I got it working. Sorry for pinging you, I didn't realize that there was documentation about the worker prop
@ccsaposs Nevermind. This whole thing is arguably confusing.
Okay, it looks like I didn't get it working all the way. Right now, I'm running into an issue on line 8552 of dist/VueQrcodeReader.umd.js, version 2.3.14. Error from minfied file: TypeError: t is not a constructor
(I believe this corresponds to Worker
on line 8552 of the non-minified file.
I did some testing after import Worker from '@/assets/js/jsqr.js'
, and it looks like Worker
is a function. Is that how it should be?
@gruhn Do you know how to fix this issue?
Thank you for your time.
Yes, Worker
is a function.
Let me repeat the full explanation in more detail here. Also because the naming is confusing. jsqr.js
and jsQR.min.js
are completely different files.
Download jsQR.min.js and put into a static asset directory such that it's directly served by your web server / dev server. So you COULD (don't actually do it) embed it with a regular script tag like this:
<script src="/my-static-directory/jsQR.min.js"></script>
Copy this file into your Vue project (not necessarily your static assets directory). Let's call this file worker.js
from now on. Now change the path here
to
self.importScripts(
self.location.origin + "/my-static-directory/jsQR.min.js"
)
AND CHANGE NOTHING ELSE. Note that the code of the function that is passed to inlineWorker
is literally converted to a String and parsed with a regular expression. Merely using an arrow function there instead would break everything. If webpack is processing this file in a weird way that might also break the code. Maybe its safer to just store the code as a string directly (you loose transpilation, syntax highlighting and so on though).
const inlineWorker = functionBody => {
return new Worker(
URL.createObjectURL(new Blob([functionBody], { type: "text/javascript" }))
);
};
export default () => {
/* eslint-disable no-undef */
return inlineWorker(`
self.importScripts(
self.location.origin + '/my-static-directory/jsQR.min.js'
);
self.addEventListener("message", function(event) {
const imageData = event.data;
const result = jsQR(imageData.data, imageData.width, imageData.height);
let content = null;
let location = null;
if (result !== null) {
content = result.data;
location = result.location;
}
const message = { content, location, imageData };
self.postMessage(message, [imageData.data.buffer]);
});
`);
/* eslint-enable */
};
Finally import the default export from worker.js
and pass that as-is via the worker
prop to the component.
<template>
<qrcode-stream :worker="Worker" />
</template>
<script>
import { QrcodeStream } from 'vue-qrcode-reader'
import Worker from './path/to/worker.js'
export default {
components: { QrcodeStream },
data() {
return {
Worker,
}
}
}
</script>
@gruhn Thank you for your response!
I went through your steps and confirmed that matched what I did. I'm still running into the same issue:
I also tried storing the code as a string and ran into the same issue.
Ok, I think your initial point might be the key here. We construct an instance of the passed worker with the new
keyword but this does not work for arrow functions. I think the reason we haven't noticed this yet, is that arrow functions get usually transpiled to regular functions. From the keyword modern
in your error message, I infer that this is not true in your case.
Thank you for looking into this!
I think I have an idea of what you're saying, but I don't understand much. At this time, is there any action I should take or any information I can provide to help?
Thank you again!
I already pushed a fix attempt but the pipeline is stalled for some reason: https://travis-ci.org/github/gruhn/vue-qrcode-reader/jobs/744525998
I'll give an update when it's through.
Ok, please check if this works for you:
npm install vue-qrcode-reader@2.3.15-alpha.1
It works! Thank you!
cool, non-alpha release is out: npm i vue-qrcode-reader@2.3.14
Hey there !
I'm sorry to reopen this subject, but it seems I'm having the same issue using the 2.3.16 version. Got a message "Failed to execute 'importScripts' on 'WorkerGlobalsScope'..." Didn't understand all the answers above, so I'm probably doing something wrong, any idea what it could be ?
The central issue is still not resolved. The new version mentioned above only fixed an issue with the given workaround. You still have to follow these steps (have you tried that already?):
Yes,
Worker
is a function.Let me repeat the full explanation in more detail here. Also because the naming is confusing.
jsqr.js
andjsQR.min.js
are completely different files.1)
Download jsQR.min.js and put into a static asset directory such that it's directly served by your web server / dev server. So you COULD (don't actually do it) embed it with a regular script tag like this:
<script src="/my-static-directory/jsQR.min.js"></script>
2)
Copy this file into your Vue project (not necessarily your static assets directory). Let's call this file
worker.js
from now on. Now change the path hereto
self.importScripts( self.location.origin + "/my-static-directory/jsQR.min.js" )
AND CHANGE NOTHING ELSE. Note that the code of the function that is passed to
inlineWorker
is literally converted to a String and parsed with a regular expression. Merely using an arrow function there instead would break everything. If webpack is processing this file in a weird way that might also break the code. Maybe its safer to just store the code as a string directly (you loose transpilation, syntax highlighting and so on though).const inlineWorker = functionBody => { return new Worker( URL.createObjectURL(new Blob([functionBody], { type: "text/javascript" })) ); }; export default () => { /* eslint-disable no-undef */ return inlineWorker(` self.importScripts( self.location.origin + '/my-static-directory/jsQR.min.js' ); self.addEventListener("message", function(event) { const imageData = event.data; const result = jsQR(imageData.data, imageData.width, imageData.height); let content = null; let location = null; if (result !== null) { content = result.data; location = result.location; } const message = { content, location, imageData }; self.postMessage(message, [imageData.data.buffer]); }); `); /* eslint-enable */ };
3)
Finally import the default export from
worker.js
and pass that as-is via theworker
prop to the component.<template> <qrcode-stream :worker="Worker" /> </template> <script> import { QrcodeStream } from 'vue-qrcode-reader' import Worker from './path/to/worker.js' export default { components: { QrcodeStream }, data() { return { Worker, } } } </script>
I've spent a couple of hours trying to do this on a Cordova Android app. The workaround with the custom worker mentioned here does not work on Cordova. importScripts is unable to load the jsqr.js file. You need to use the jsQR package in your worker.
Instead, use this worker code:
import jsQR from "jsqr";
self.addEventListener("message", function(event) {
const imageData = event.data;
const result = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert"
});
let content = null;
let location = null;
if (result !== null) {
content = result.data;
location = result.location;
}
const message = { content, location, imageData };
self.postMessage(message, [imageData.data.buffer]);
});
@Techn1c4l could you make it work that way or are you proposing this solution?
The problem is to instantiate a WebWorker you need to pass a file path, Usually a JavaScript file
new Worker("./worker-source-code.js")
Because workers don't support import jsQR from "jsqr"
directly we have two options:
importScripts
as we do nowPoint two is appealing but let me quote myself on that:
As I said this is not trivial.
You can't just import dependencies in web workers. You can
importScripts
as we are doing right now. Or bundle all dependencies together with the worker source code. For that matter you probably need a dedicated webpack loader like worker-loader and this should ideally be incorporated into the Vue CLI boilerplate config. You probably need to do that with webpack chaining.If you manage to implement this or can find a better solution, please open a pull request.
@gruhn The code I posted works both on web development server and on Android. I just installed the jsQR package and put the code above in my worker file. And it works alright.
Have you tried non-Chrome based browsers?
https://caniuse.com/mdn-javascript_statements_import_worker_support
@gruhn I work on the Cordova Android project, so no, I didn't try. By the way, the solution I'm suggesting was taken from https://github.com/gruhn/vue-qrcode-reader/blob/91ee3fc8bf2f7fab96ac3f0a5d84d2d4c09b012f/src/worker/jsqr.js.
So if I understand correctly, this issue does not have a 100%-working solution for every case. You can't use importScripts
on Cordova because it results in Uncaught DOMException: Failed to execute 'importScripts'
error. And we can't use import
in web worker environment.
Edit: Well, it seems like just copying and pasting the whole jsQR source code into the worker file is the only reliable solution for every case.
:tada: This issue has been resolved in version 3.0.0 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
Describe the bug
There the breaking change was introduced in
v2.3.10
. The dependency of thewebrtc-adapter
package has been removed. Now it's impossible to usevue-qrcode-reader
in local environments (without internet connection) and in applications with strict CSP rules (loading scripts from external resources is not allowed).Screenshots