What I built (4) — browser extension to generate QR Code

Stephen Cow Chau
Nerd For Tech
Published in
5 min readMar 15, 2021

This is a browser extension that generate QR code for selected text or current page URL.

Intention and Background

This is a project that I rethink about the OCR solution in:

Problem in OCR solution was the accuracy and the speed is not satisfactory, and so consider the same problem to copy text from PC and use it in mobile phone, one way was to use a middle man like Signal, WhatsApp, email…But my take is to do it with camera (OCR in previous solution or QR code scanning in this solution).

What it does

It display the QR code of the current URL or selected text and allow mobile phone to scan it to get the text.

The pop up on upper right (upon clicking the extension icon), with the buttons clicked, it convert either the current URL or selected text to a QR code and display in this panel

Major technology used

  • React.js with “create-react-app”
  • “customize-cra” package to create multiple entry points (as create-react-app by default provide single entry point)
  • “qrcode” package to generate QR code

Getting Start

The main reference I followed is:

Why do I need multiple entry point

The answer is the current implementation does not yet require that, but I feel it might be needed in future, and so I better prepare for it. People might suggest using webpack to manage the solution packaging, I am just not experience enough to manage webpack and the create-react-app have default settings that should do well, and with help of customize-cra does serve the purpose, which is my “go to” for now, and after all, learning something new is exciting.

Code/config snippets

Manifest.json

My manifest file is like following:

{
"name": "Selected Text QR Extension",
"version": "0.0.0.1",
"manifest_version": 2,
"description": "extensions to generate QR code for selected text",
"icons": {
"128": "logo192.png"
},
"permissions": ["activeTab", "tabs"],
"browser_action": {
"default_icon": "logo192.png",
"default_popup": "pages/popup.html"
},
"options_ui":{ "page": "pages/options.html" }
}

Honestly I am not sure the permission defined is a bit too much, but I am using the browser.tabs.executeScript() to call “window.getSelection().toString()”, I am betting that this is not the most appropriate way to get text being selected, but this is the first solution I found for now, and that leads me to add “tabs” permission.

I did add 2 routes (pages), the “pages/popup.html” would be called to open the main pop up page, and pages/options.html is for the option_ui page (which is a dummy for now, but should be something else in future enhancement).

Also seems “options_page” in manifest behave differently from “options_ui”, which I have no idea what is expected from “options_page” yet.

The dummy option ui page.

config-overrides.js

This file is for the customize-cra, the multipleEntry is to allow create-react-app to have multiple entry point (instead of just “/”), the result would copy the “entry” and “template” file to “outPath” .

The “copyPlugin” is to copy the non-entry point files indicated in config to “build” folder root, as our extension would be zipped and these files is expected to be found at root of the zip, the “to” is empty string to indicate root.

The rest is from the reference webpage and I haven’t touch them.

const {
override,
overrideDevServer,
addWebpackPlugin
} = require("customize-cra");
const CopyPlugin = require('copy-webpack-plugin');
const multipleEntry = require('react-app-rewire-multiple-entry')([
{
// points to the popup entry point
entry: 'src/popup/index.js',
template: 'public/pages/popup.html',
outPath: 'pages/popup.html'
},
{
// points to the options page entry point
entry: 'src/options/index.js',
template: 'public/pages/options.html',
outPath: 'pages/options.html'
}
]);
const devServerConfig = () => config => {
return {
...config,
// webpackDevService doesn't write the files to desk
// so we need to tell it to do so so we can load the
// extension with chrome
writeToDisk: true
}
}
const copyPlugin = new CopyPlugin({
patterns: [
// copy assets
{ from: 'public', to: '' },
{ from: 'src/background.js', to: '' },
// { from: 'src/content.js', to: '' }
{ from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js' },
]
})
module.exports = {
webpack: override(
addWebpackPlugin(
copyPlugin
),
multipleEntry.addMultiEntry,
),
devServer: overrideDevServer(
devServerConfig()
),
};

Folder structure

The folder structure are mostly follow what create-react-app provisioned, except the following:

Root
> build/ // this is create by webpack
> node_modules/
> public/
> pages/ // this is created to host the "templates" html
> options.html // copy of the index.html of create-react-app
> popup.html // copy of the index.html of create-react-app
> ...
> src/
> options/ // the files below are copy from create-react-app
> App.js // modified to serve option page component
> App.test.js
> index.js // entry point of options page
> popup/
> ... // similar to options above
... // the rest from create-react-app
> config-overrides.js // the override config file for customize-cra
... // the rest from create-react-app
to compliment the above explanation

Note that the src folder level still have App.js and index.js even they are not used, without them the build does not work, they can be empty files.

Core code to get select text and generate QR code

This is the part I felt I am not doing the best way (at least from security stand point as I use browser.tabs.executeScript which I feel it would be restricted in future), below is code for the button click event handler.

function handleClickGetUrl() {
browser.tabs.executeScript( {
code: "window.location.href.toString();"
}).then(text => {
QRCode.toCanvas(canvasRef.current, text);
})
}

Conclusion

The happiest took away for myself is getting to know the way to allow multiple entry for create-react-app with customize-cra, that could be pretty useful in my other projects.

--

--