Sec-ant / barcode-detector

A Barcode Detection API polyfill that uses ZXing-C++ WebAssembly under the hood.
https://www.npmjs.com/package/barcode-detector/v/latest
MIT License
69 stars 6 forks source link

Barcode detection service unavailable #18

Open louie0086 opened 9 months ago

louie0086 commented 9 months ago

https://github.com/Sec-ant/barcode-detector/blob/ab876a6a19b610ed3ce4b69f8216ed3c878421f4/src/BarcodeDetector.ts#L140-L143

i used vue-qrcode-reader at https website

<qrcode-stream @detect="onDetect"></qrcode-stream>

it shows "Barcode detection service unavailable. Use 'setZXingModuleOverrides' in offline or strict CSP environments.", "NotSupportedError"

the host already set Strict-Transport-Security: max-age=31536000; includeSubdomains; preload

but the static js file not set

i see the demo website https://vue-qrcode-reader.netlify.app. it's doc and static js both set Strict-Transport-Security

how can i solve the detect error?

Sec-ant commented 9 months ago

Hey, louie

I don't think Strict-Transport-Security is relevant here. "CSP" is short for "Content Security Policy".

This package uses WebAssembly to provide the core function of detecting barcodes. And the browser must instantiate and execute the WebAssembly module to use it. However, per the spec, websites can control whether to block the WebAssembly execution by setting the script-src directive in CSP, specifically the wasm-unsafe-eval or unsafe-eval source value. If it is blocked, the error Barcode detection service unavailable will get thrown.

The directive connect-src can also affect this package. To prevent bloating the size, this package will load the .wasm file from the cdn.jsdelivr.net CDN at runtime (using fetch). connect-src or default-src can restrict the URLs which can be loaded with fetch. So if this connection is somewhat get blocked, the service unavailable error will also be thrown.

There're corresponding ways in which you can solve this problem:

If neither solves your problem, it would be great if you can provide the malfunctioning website so that I can dig into it and check whether there're some other bugs to fix.

louie0086 commented 9 months ago
import wasmFile from "../node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url";

I would like to ask what the syntax (?url)is?

in vue-cli project

import wasmFile from "@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url";

it's show error:

* a in ./node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url

To install it, you can run: npm install --save a

How do I import the wasm module correctly?

Sec-ant commented 9 months ago

Ok you decide not to fetch the .wasm file from jsDelivr. There're some gotchas, so I'd like to explain more:


First, a question: I wonder if you want to inline the .wasm file inside your script output files or you want to host it as a binary file on your site, and fetch it from your site at runtime?

Inline

Inlining means encoding the .wasm file as a Base64 data url and hardcode this data url inside your script files. If you use a syntax like this:

import wasmFile from "...?url"

then you're inlining it. ?url is a syntax provided by build tools, like Vite, to transform assets into data urls. I'm not sure if vue-cli supports it. If it is supported, then the wasmFile will be a huge base64 encoded data url.

Afterwards, you use setZXingModuleOverrides + locateFile to relocate the fetching path to this data url:

setZXingModuleOverrides({
  locateFile: (path, prefix) => {
    if (path.endsWith(".wasm")) {
      return wasmFile; // <= a huge Base64 encoded data url
    }
    return prefix + path;
  },
});

A data url occupies a larger space than its binary form, and parsing a data url into binary data also consumes more CPU clocks. So, ideally, I suggest you to not use the inline form.

Binary

If you want to host it as a binary file on your site, then you need not to import it. You can just copy the .wasm file to your website's assets folder, e.g. public or assets, and use setZXingModuleOverrides + locateFile to relocate the fetching path like this:

setZXingModuleOverrides({
  locateFile: (path, prefix) => {
    if (path.endsWith(".wasm")) {
      return "https://<your website host>/public/zxing_reader.wasm"; // <= where the .wasm file is at
    }
    return prefix + path;
  },
});

Either way, CSP still applies. You have to make sure content-src and script-src are correctly configured to allow you fetching and executing it.


Second question: where is the .wasm file?

Normally, when you add vue-qrcode-reader as a dependency in your project, it will also download its dependencies inside node_modules folder, which includes barcode-detector. And barcode-detector in turn will download its dependency @sec-ant/zxing-wasm. This process is handled by the package manager automatically so you don't need to worry.

The .wasm file is published as an asset included in the package @sec-ant/zxing-wasm, therefore, the .wasm file can be found at this path:

<project root>/node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm

BUT, I made a mistake not pinning the version. So, the .wasm file you found in <project root>/node_modules/@sec-ant/zxing-wasm may not be what vue-qrcode-reader expects (Sorry for this mistake, I'm going to fix this in the next version). Therefore, for now, I recommend you manually download the .wasm file with the correct version, from the CDN.

If you're using vue-qrcode-reader@v5.3.4, the matching version is @sec-ant/zxing-wasm@2.1.3, so you can download the zxing_reader.wasm file from any of the following CDN paths:

Once you've downloaded the correct version of the .wasm file, you can inline or host it as you wish in your project.

Again, I'm really sorry for the inconvenience. Once the version problem is fixed and released in vue-qrcode-reader, you won't have to worry about downloading it with the matching version. You can just import or copy it from <project root>/node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm.

louie0086 commented 9 months ago

Thank @Sec-ant

i just put zxing_reader.wasm at assets public folder and then

  setZXingModuleOverrides({
    locateFile: (path, prefix) => {
      if (path.endsWith(".wasm")) {

        return '/zxing_reader.wasm';
      }
      return prefix + path;
    },
  });

my project site can request zxing_reader.wasm now.but it's still has error "Barcode detection service unavailable. Use 'setZXingModuleOverrides' in offline or strict CSP environments."

if the zxing_reader.wasm not from CDN and CSP not configured .Is this error still here?

Sec-ant commented 9 months ago

Then this may be some other bug. To solve this problem I'll need more information.

louie0086 commented 9 months ago

Then this may be some other bug. To solve this problem I'll need more information.

  • If this issue is OS/browser related For example, is the demo page of vue-qrcode-reader works on the same device? If it doesn't, then it's probably device related, and then I need the versions of the device's OS and browser.
  • If the error only occurs in your project site Then it would be very helpful for you to share a repo to let me reproduce this problem, or share your site so that I can debug.

yes.same device. vue-qrcode-reader demo page no exception

Sec-ant commented 9 months ago

In that case, can you share a repro :)

louie0086 commented 9 months ago

In that case, can you share a repro :)

Ok .i send email to you

Sec-ant commented 9 months ago

Ok .i send email to you

Alright, here's my email address: zzwu@zju.edu.cn

Sec-ant commented 9 months ago

@louie0086 Can you try vue-qrcode-reader@5.3.5 (matches with @sec-ant/zxing-wasm@2.1.5) and see if this error still exists? If the error still exists, can you find more error information in the console?

louie0086 commented 9 months ago

@louie0086 Can you try vue-qrcode-reader@5.3.5 (matches with @sec-ant/zxing-wasm@2.1.5) and see if this error still exists? If the error still exists, can you find more error information in the console?

i updated @sec-ant/zxing-wasm@2.1.5 and copy zxing_reader.wasm to zxing_reader1.wasm

still

  setZXingModuleOverrides({
    locateFile: (path, prefix) => {
      if (path.endsWith(".wasm")) {

        return '/zxing_reader1.wasm';
      }
      return prefix + path;
    },
  });

now it show

    "Barcode detection service unavailable.",
    "NotSupportedError"
Sec-ant commented 9 months ago

I added this line of code to log a detailed error information, can you some how see this in the console?

https://github.com/Sec-ant/barcode-detector/blob/bca5f666062e29a8a4d075f71e4695f9e7fc77c1/src/BarcodeDetector.ts#L156-L157

louie0086 commented 9 months ago

I added this line of code to log a detailed error information, can you some how see this in the console?

https://github.com/Sec-ant/barcode-detector/blob/bca5f666062e29a8a4d075f71e4695f9e7fc77c1/src/BarcodeDetector.ts#L156-L157

image

Sec-ant commented 9 months ago
screenshot

The RangeError is what we've been seeking. Is that just an empty object or does it hold more information?

louie0086 commented 9 months ago
screenshot

The RangeError is what we've been seeking. Is that just an empty object or does it hold more information?

message:Range consisting of offset and length are out of bounds.

stack:set@[native code]

Sec-ant commented 9 months ago

Hmm, interesting.

If this is something about the WebAssembly instantiation, I wonder why vue-qrcode-reader demo page works.

Does this error message have a context so that I can check which line of code and file this error is thrown from? Or can you jump to the corresponding file and provide some information on its whereabouts? Screenshots would be great, and you can email me if you want.

PS: I previously said

Once the version problem is fixed and released in vue-qrcode-reader, you won't have to worry about downloading it with the matching version. You can just import or copy it from /node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm.

The good news is, starting from vue_qrcode_reader@5.3.5, this version problem is fixed :).

louie0086 commented 9 months ago

I isolated a stripped down code from my project that is scannable. But the original project still has Range Error and NotSupportedError.

https://github.com/louie0086/test_Barcode

i used mkcert and http-server -S -C {PATH/TO/CERTIFICATE-FILENAME}.pem -K {PATH/TO/CERTIFICATE-KEY-FILENAME}.pem

https://web.dev/i18n/en/how-to-use-local-https/

with local IP test

Sec-ant commented 9 months ago

Thanks for the repo, but I'm afraid a scannable demo can't provide to too much information on why your original project doesn't work.

What about these questions I previously asked:

Does this error message have a context so that I can check which line of code and file this error is thrown from? Or can you jump to the corresponding file and provide some information on its whereabouts?

louie0086 commented 9 months ago

RangeError stack here:

https://github.com/louie0086/test_Barcode/blob/5c7a69936a4649c5b860b405b6797ba5aef3456c/public/barcode-detector/dist/es/pure.js#L345-L354

 $.HEAP8.set(O, L)
Sec-ant commented 9 months ago

RangeError stack here:

https://github.com/louie0086/test_Barcode/blob/5c7a69936a4649c5b860b405b6797ba5aef3456c/public/barcode-detector/dist/es/pure.js#L345-L354

 $.HEAP8.set(O, L)

Thanks, this is very helpful. So the error is thrown from this line from the source code:

https://github.com/Sec-ant/zxing-wasm/blob/8b8df68e781e7524ce96f12276c7a028b2c0c9d6/src/core.ts#L390

I'm not very clear about the root cause but I can try to do some debugging. So this error happens on an iPhone iOS 16.6 Safari Browser, right?

louie0086 commented 9 months ago

RangeError stack here: https://github.com/louie0086/test_Barcode/blob/5c7a69936a4649c5b860b405b6797ba5aef3456c/public/barcode-detector/dist/es/pure.js#L345-L354

 $.HEAP8.set(O, L)

Thanks, this is very helpful. So the error is thrown from this line from the source code:

https://github.com/Sec-ant/zxing-wasm/blob/8b8df68e781e7524ce96f12276c7a028b2c0c9d6/src/core.ts#L390

I'm not very clear about the root cause but I can try to do some debugging. So this error happens on an iPhone iOS 16.6 Safari Browser, right?

yes.iPhone 14 Pro Max.

But the demo repo is Okay

Sec-ant commented 9 months ago

Can you log the data, byteLength, bufferPtr and HEAP8 if you can patch the source packages in your project?

image

You can also directly modify the dist file, like this:

image

And show me the result?

louie0086 commented 9 months ago

img_v2_b4a0580b-329e-45fc-a828-4353af1f5chu

Sec-ant commented 9 months ago

What's the byteLength of the Int8Array, i.e. zxingInstance.HEAP8.byteLength?

louie0086 commented 9 months ago

byteLength

i just console at barcode-detector.pure.js

    const w = await Bt($, ot.getState().zxingModuleOverrides),
      {
        data: O,
        width: G,
        height: j,
        data: { byteLength: F },
      } = c,
      Y = w._malloc(F)
    console.log('data:',O)
    console.log('byteLength:',F)
    console.log('bufferPtr:',Y)
    console.log('zxingInstance.HEAP8:',w.HEAP8)
    w.HEAP8.set(O, Y)
    const N = w.readBarcodesFromPixmap(Y, G, j, m, pe(u), g)
    w._free(Y)
Sec-ant commented 9 months ago

I mean, can you also log the byteLength of the HEAP8 object in the console? So I can know the total available memory it holds.

like this (add this line):

console.log('zxingInstance.HEAP8.byteLength:', w.HEAP8.byteLength);
louie0086 commented 9 months ago
w.HEAP8.byteLength

the result: zxingInstance.HEAP8.byteLength:0

Sec-ant commented 9 months ago

That's some useful info and a good start. Apparently, for some reason, the memory is not successfully allocated, which brings us to this line of code:

https://github.com/louie0086/test_Barcode/blob/5c7a69936a4649c5b860b405b6797ba5aef3456c/public/barcode-detector/dist/es/pure.js#L415-L416

Can you help me check whether J.buffer.byteLength is also zero?

louie0086 commented 9 months ago

That's some useful info and a good start. Apparently, for some reason, the memory is not successfully allocated, which brings us to this line of code:

https://github.com/louie0086/test_Barcode/blob/5c7a69936a4649c5b860b405b6797ba5aef3456c/public/barcode-detector/dist/es/pure.js#L415-L416

Can you help me check whether J.buffer.byteLength is also zero?

https://github.com/louie0086/test_Barcode/blob/master/barcode-detector.pure_4.js#L443

K.buffer undefiend

Sec-ant commented 9 months ago

Hmmm, that means wasmMemory is not initiated.

Can you log n, n.exports and n.exports.qa in this function before the return statement?

https://github.com/louie0086/test_Barcode/blob/2faebca6c2e0ed3626b7ebfa183937b76cc279fe/barcode-detector.pure_4.js#L564-L566

louie0086 commented 9 months ago

20230929-140203 20230929-140207

https://github.com/louie0086/test_Barcode/blob/f1834f83f809259f7038b52557df0a9f3df58d93/barcode-detector.pure_4.js#L564-L566

Sec-ant commented 9 months ago

In a normal case, the n.exports.qa should be an instance of WebAssembly.Memory, like this (in a chrome browser):

image

I cannot tell whether B$() in your snapshot is an instance of the WebAssembly.Memory or is just a plain function. Given that the code works in your demo repo but not in your project, can you also check the n.exports.qa in your demo repo and compare the working one with the error one to see if the difference indeed lies in the n.exports.qa?

louie0086 commented 9 months ago

https://github.com/louie0086/test_Barcode/blob/master/barcode-detector.pure_4.js#L564-L566

i add a alert

The demo tips [object WebAssembly.Memory]

but the project is function B$(){hC()}