Cookies & Messaging Browser Extension

This is a fully documented & tested implementation of a cross-browser extension that locates a domain-specific cookie, using the messaging API common to both Chrome + Firefox to inject that cookie’s value into the DOM of the page in the active tab.

The code has embedded documentation that explains the overall purpose of the extension, the logic of code blocks and the interaction between the extension’s files.

I hope you enjoy it.

NOTE - In order to better see delimiters like commas & semicolons in the code blocks, switch to dark mode using the theme switcher at the top of the page.

Bill of Materials

manifest.json background.js content.js favicon.png

manifest.json:

{
  "manifest_version": 2,
  "name": "Cookies & Messages",
  "description": "List all cookies and pass a matched cookie in a message. Copyright 2016-2021 John Minnihan",
  "author": "John Minnihan",
  "version": "1.0",
  "permissions": ["cookies","<all_urls>","tabs"],
  "background": {
    "scripts": ["background.js"]
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": ["content.js"]
    }
  ],
  "browser_action": {
    "default_icon": "favicon.png"
  }
}

background.js:

  // file name: background.js

  // this file is part of the Cookies & Messages browser extension
  // Copyright 2016 - 2021 John Minnihan
  // https://jbminn.com

  // extension package:
  //  manifest.json
  //  background.js
  //  content.js
  //  favicon.png

  // favicon.png is a comment bubble image

  // INSTRUCTIONS

  // install all four files into your browser's extensions manager
  // when installed, it will be named Cookies & Messages and
  // have a comment bubble icon as its browser action (invoker button)

  // customize the below two regex consts per your requirements

  // in this case we are matching first on all cookies in the twitter.com domain
  // then from that set of cookies we pull the cookie named 'personalization_id'
  // and send its value in a message to the console of the active tab

  // this places the cookie into the DOM of whatever page we are visiting, i.e. a login page

  // from there, we can inject it into a div in that page to satisfy logic that requires
  // the value of the cookie, i.e. an access token

  // the counters are useful in understanding logic flow and counting cookies in a domain
  // but they play no role in the core functionality

  // the console.log() statements can be safely commented or removed

  const API = chrome || browser;

  const regex = new RegExp('twitter');
  const regex2 = new RegExp('personalization_id');
  var matchedCookie;
  var matchedCookieValue;
  var mainCounter;
  var internalCounter;

  // Called when the user clicks on the extension's icon in the toolbar

  // IMPORTANT - the first time the extension's icon is clicked, the console will log this:

  //      Message received from background:  undefined

  // that is expected and is due to there being no message to receive until the cookie(s) are located.
  // subsequent invocations of the extension will log the located cookie in JSON notation in the console

  // you could enhance this by integrating the message handling into the first tab() function

  API.browserAction.onClicked.addListener(function(tab) {

   mainCounter = 1;
   console.log('Main iteration: ', mainCounter);

   // first we query all the tabs of the current browser window
   // the hierarchy is window => tab => content (DOM)

   API.tabs.query({windowId: API.windows.WINDOW_ID_CURRENT}, (tabs) => {

    // once we have the tabs, we get all the cookies and then extract their contents
    // to make them easier to use in the page to which we're sending them

      API.cookies.getAll({}, function(cookies) {

        internalCounter = 1;
        // instantiate an empty array to hold the cookie properties
        let c = [];
        cookies.forEach(function(cookie) {

         // if you want to see the cookie get deserialized, uncomment the next console.log() line

         // each cookie will be printed to the extension's background page console.  
         // you gain access to this by opening up the extension manager in the browser and
         // clicking the link for the background page in the extension's details panel

         // console.log("Cookie " + internalCounter + ": " + JSON.stringify(cookie));

         // pull the cookie's properties out and place them into the new array
         // this allows us to easily use the cookie's name and/or value in the message
         // we send to the active tab

            c.push({
               name: cookie.name,
               value: cookie.value,
               domain: cookie.domain,
               secure: cookie.secure,
               path: cookie.path
            });

            internalCounter++;

        });

        console.log('The cookies array length is: ', c.length);

        // now we use a for(..) loop to walk through the array of cookies
        // and test for the domain name in our first regex
        
        for (let j = 0; j < c.length; j++) {

           if (regex.test(c[j].domain)) {

             // this next console.log() is extremely verbose for domains with a lot of cookies
             // console.log(count + ' - Matching cookie for the domain: ', c[j]);

             // if we match the domain name, we now check the cookie's name to see if it matches
             // the pattern in our regex2
             
             if (regex2.test(c[j].name)) {

              // if the cookie's name matches our regex2, we put its full set of properties
              // in the matchedCookie variable and just the cookie's value property into the
              // matchedCookieValue variable

              // we'll use the matchedCookieValue variable in the message we send to the content

               matchedCookie = c[j];
               matchedCookieValue = c[j].value;
               console.log('Matched cookie: ', matchedCookie);
               console.log('Matched cookie value', matchedCookieValue);
             } 
           }
        }

      });

      // Send the cookie in a message to the active tab

      // in the accompanying file content.js that is part of this extension, a function to
      // listen for the message is running as soon as we invoke the extension, i.e. click on its
      // icon in the extension action bar

      API.tabs.query({active: true, currentWindow: true}, function(tabs) {

        // grab the active tab object so we can use its 'id' property in the sendMessage()
        let activeTab = tabs[0];

        // console log this during development so you can see the message flow
        console.log('Sending message: \"Message from background: \" ' + matchedCookieValue);

        // now send the message to the active tab + watch for it in the console of that tab's page 
        API.tabs.sendMessage(activeTab.id, {"message": matchedCookieValue});

      });

    });
    mainCounter++;
  });

content.js:

  // file name: content.js

  // this file is part of the Cookies & Messages browser extension
  // Copyright 2016 - 2021 John Minnihan
  // https://jbminn.com

  // extension package:
  //  manifest.json
  //  background.js
  //  content.js
  //  favicon.png

  // favicon.png is a comment bubble image

  // see INSTRUCTIONS in background.js

  const API = chrome || browser;

  function printObj(o) {
    var out;
    for (var p in o) {
      out += p + ': ' + o[p] + '\n';
    }
    console.log(out);
  }

  API.runtime.onMessage.addListener(

    function(request, sender, sendResponse) {
      // if you have a complex cookie object that you need to inspect visually
      // during development, uncomment the printObj() line

      // recall that the request coming in is an object but we are only dealing with one property of that object, the message

      // that message property is an array.  recall how we created it in background.js?

      // the printObj() function will return the output for every item in an array
      // this can be excessive for arrays that consist of multiple single character items that
      // when printed will look like a stack of single letters followed by line-feeds

      // sometimes this is useful and sometimes it isn't

      // compare a plain console.log() of the message property to
      // a printObj(..) of the message property

      // console.log('Incoming response object message property: ', request.message);
      // printObj(request.message);

      console.log('Message received from background: ', request.message);

      // the below line illustrates how to inject the message we just received - the cookie value - into
      // the DOM of the active tab's page

      // keep in mind that the active tab's page is not part of the extension.  you use the extension to inject 
      // a cookie's value into that page.  it is a plain HTML page that uses the extension when you click its
      // browser action (i.e. the comment bubble icon) in the extensions toolbar area

      // in this case, we require a <div> on that page with id="myDiv", i.e.

      // <div id="myDiv">We have not yet received a message</div>

      // when the innerHTML method below is run, the div's content will be replaced with the message, which
      // is the value of the cookie we located over in background.js

      // using these techniques, we have located all cookies by domain, then found a specific named cookie from that domain,
      // extracted the value of that cookie and then sent it using the message API to the content, i.e. the active tab's page.

      // finally, we update the div on the active tab's page with that value
      document.getElementById('myDiv').innerHTML = 'Cookie value: ' + request.message;
    }
  );
0%