Search code examples
javascripttypescriptbrowser-extension

How to Access a Global Variable through TypeScript in a Browser Extension Context (and partially type it if possible)


1. The Problem

I'm trying to access a global variable from a website — random OGS Example — through TypeScript in order to get data from the current board game.

I'm pretty new to TS so I'm not really sure how I would type this variable either. The global variable itself was originally created in TS actually, but it has a bazillion methods and properties, since there are so many functionalities. So, if I'm going to type it myself, I would rather only type what I need, if that's possible, or somehow import it from a package somewhere — I don't think the author has published it publicly anywhere.

In parallel to all this, there's also the question of whether or not global variables are available by default to browser extensions, which might not be the case.

2. What I've Been Trying

So, what I've been trying (I've tried a million other variations...) to do is something like* — inside content.ts, which is run when inside the OGS domain —:

interface Goban {
  bounded_height: number;
}

declare var goban: Goban;

goban = window["global_goban"];

console.log(goban.bounded_height.toString());

The error I usually get is:

Uncaught ReferenceError: goban is not defined

So what am I missing? Do I have to create a declaration file (.d.ts)? What is the proper way of dealing with this sort of import of a global variable from 3rd party code?

*: Depending on how you're trying to deal with the window object, you might have to add "suppressImplicitAnyIndexErrors": true to your tsconfig.json in order to have TS not complaining statically.

3. How to Fully Reproduce My Problem

For those unfamiliar with how to develop browser extensions, the simplest way to start, in TS, is to:

  1. Create a simple manifest.json, for example:
    {
        "manifest_version": 2,
        "name": "Name",
        "version": "0.0.2",
        "description": "Description.",
        "content_scripts": [
            {
                "matches": ["*://*.online-go.com/*"],
                "js": ["content.js"]
            }
        ]
    }
    
  2. Create a content.ts file, with the code from the previous section.
  3. Compile to JS with tsc.
  4. Import the manifest.json and the content.js into a browser as an unpacked browser extension.
  5. Load a random OGS game and inspect it.

Solution

  • 1. The Gist of It

    Content scripts can't access global variables directly, they work in an "isolated world". From the Content Script Environment MDN Docs:

    However, content scripts get a "clean" view of the DOM. This means:

    • Content scripts cannot see JavaScript variables defined by page scripts.

    But you can still work around it by sharing resources through the web_accessible_resources key in your manifest.json.

    2. One Possible Workaround

    Here is one way how this could be done:

    manifest.json:

    {
      "manifest_version": 2,
      "name": "Name",
      "version": "0.0.2",
      "description": "Description.",
      "content_scripts": [
        {
          "matches": ["*://*.online-go.com/*"],
          "js": ["content.js"]
        }
      ],
      "web_accessible_resources": ["script.js"]
    }
    

    You can add whichever script you might need to the web_accessible_resources key, just like the ones used in the matches key above for example. You can also use wildcards, like *, to match several at the same time.

    You might still need to add this to your compilerOptions inside your tsconfig.json: "suppressImplicitAnyIndexErrors": true.

    script.ts:

    interface Goban {
      bounded_height: number;
    }
    
    declare var goban: Goban;
    
    goban = window['global_goban'];
    
    console.log(goban.bounded_height.toString());
    

    content.ts:

    const script = document.createElement('script');
    script.src = chrome.extension.getURL('script.js');
    document.head.append(script);
    

    You might also need to install @types/chrome through, for example, npm i --save @types/chrome.

    3. Further Resources

    Also, content scripts can communicate with page scripts using window.postMessage and window.addEventListener.

    Further resources: