Search code examples
javascriptfirefoxfirefox-addonfirefox-addon-webextensionsopenpgp.js

JavaScript: openpgp is not defined error in Firefox extension content script


Original Question:

Problem Description

I am developing a browser extension that uses OpenPGP.js to encrypt and decrypt selected text on a webpage. The extension works perfectly in Chrome, but I encounter an issue in Firefox where the openpgp object is not defined.

Also one more error in openpgp.min.js

Error: concatUint8Array: Data must be in the form of a Uint8Array

Code Snippet

Here is the relevant part of my content script:

async function encryptSelectedText() {
  const selection = window.getSelection().toString();
  console.log("Selected text:", selection);
  const selectionObj = window.getSelection().getRangeAt(0);
  console.log("Selection object:", selectionObj);

  if (selection) {
    try {
      // Get the selected public key from storage
      browser.storage.sync.get("selectedPublicKey", async function (data) {
        // console.log("Selected public key:", data.selectedPublicKey);
        const publicKeyArmored = data.selectedPublicKey;
        console.log("Public key:", publicKeyArmored);
        if (publicKeyArmored) {
          const publicKey = await openpgp.readKey({
            armoredKey: publicKeyArmored,
          });
          // further code but i get error here

Already included openpgp.js in the content scripts:

{
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["src/openpgp.min.js", "src/content.js"]
    }
  ]
}

My file structure:

├── manifest.json
└── src
    ├── background.js
    ├── content.js
    ├── openpgp.min.js
    ├── openpgp.min.js.map
    └── popup
        ├── popup.css
        ├── popup.html
        └── popup.js

Expected Behavior

The openpgp object should be defined in Firefox, just as it is in Chrome, allowing the encryption and decryption functions to work correctly.

Searched internet for related problems but no help. Also asked chatgpt but that also did't work

The openpgp object should be defined in Firefox, just as it is in Chrome, allowing the encryption and decryption functions to work correctly but it dosen't work i firefox.


Update:

After further investigation, I discovered that the issue was with how Uint8Array was being recognized in Firefox. Although the elements are Uint8Array instances, Firefox did not recognize them as such.

Following is a snippet from openpgp.js

  function isUint8Array(input) {
    return Uint8Array.prototype.isPrototypeOf(input);
  }

function concatUint8Array(arrays) {
  if (arrays.length === 1) return arrays[0];

  console.log("arrays:", arrays);

  let totalLength = 0;
  for (let i = 0; i < arrays.length; i++) {
    const element = arrays[i];
    console.log("Element at index", i, ":", element);
    console.log("Is Uint8Array:", isUint8Array(element));
    console.log("Element constructor:", element.constructor.name);
    console.log("Instance of Uint8Array:", element instanceof Uint8Array);

    if (!isUint8Array(element)) {
      throw new Error('concatUint8Array: Data must be in the form of a Uint8Array');
    }

    totalLength += element.length;
  }

  const result = new Uint8Array(totalLength);
  let pos = 0;
  arrays.forEach(function (element) {
    result.set(element, pos);
    pos += element.length;
  });

  return result;
}

It shows the following the firefox

Element at index 0 : 
Uint8Array(14) [ 79, 112, 101, 110, 80, 71, 80, 32, 88, 50, … ]
Is Uint8Array: false 
Element constructor: Uint8Array 
Instance of Uint8Array: false

This works fine in Chrome, but not in Firefox.

Also if I just return true from isUint8Array then the ecryption works but get the following error while decrypting.

Error: Permission denied to access property "constructor"


Solution

  • After referencing this discussion on github following changes solved my issue:

    Changing

    function isUint8Array(input) {
        return Uint8Array.prototype.isPrototypeOf(input);
      }
    

    to this:

    function isUint8Array(input) {
        return Uint8Array.prototype.isPrototypeOf(input) || input instanceof globalThis.Uint8Array;
      }
    

    and arr.push(result);

    to

    const fixedArray = new Uint8Array(result);
    arr.push(fixedArray);