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.

This post is an example of the type of technical content that I am producing for a paid subscription to this site. Cadence for paid posts will be once weekly while all other content will remain free.

While the code is copyrighted, you may use it as you wish.

I hope you enjoy it.

Bill of Materials

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

manifest.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "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:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 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;
  }
);