MCederholm

Create a Chrome Extension with TS/JSAPI

Blog Post created by MCederholm on Mar 16, 2020

[This was to be my user presentation at the 2020 DevSummit, which was cancelled.]

 

Chrome extensions are a fun way to implement functionality that is not normally available to a web client app. Extensions can make cross-domain requests to gather data from a variety of sources, and at the same time can filter out unwanted content. The Chrome API provides a rich suite of tools for focused application development.

 

Obviously, any app that is implemented as a Chrome extension will only run in Chrome. Also, Chrome extensions must be distributed through Chrome Web Store, but that's not necessarily a bad thing, as I will show later.

 

Here are some online resources:

 

 

Chrome extensions can contain background scripts, content scripts, a UI for saved user options, and so on. The manifest file is what ties it all together: if you've developed custom widgets for Web AppBuilder, you should already be familiar with the concept. Here's an example of manifest.json:

 

{
     "name": "Simple Map Example",
     "version": "1.0",
     "description": "Build an Extension with TypeScript and the ArcGIS API for JavaScript 4.x!",
     "manifest_version": 2,
     "icons": { "128": "images/chrome32.png" },
     "browser_action": {
          "default_popup": "popup.html",
          "default_icon": { "128": "images/chrome32.png" }
     },
     "options_ui": {
          "page": "options.html",
          "open_in_tab": false
     },
     "permissions": [ "storage" ],
     "content_security_policy": "script-src 'self' https://js.arcgis.com blob:; object-src 'self'"

}

 

One thing that's worth pointing out is the "content_security_policy" entry. This will be different depending on whether you use JSAPI 3.x or 4.x. See this post for more information.

 

Let's use a Visual Studio 2017 project template (attached) to create a simple extension. Because the template uses TypeScript, there are some prerequisites; see this post for more information.

 

First, let's create a blank solution called DevSummitDemo:

 

 

Next, add a new project using the ArcGIS4xChromeExtensionTemplate:

 

 

Here is the structure of the resulting project:

 

 

Building the project compiles the TypeScript source into corresponding JS files.  Extensions can be tested and debugged using Chrome's "Load unpacked" function:

 

 

Note that Chrome DevTools will not load TypeScript source maps from within an extension. That's normally not an issue since you can debug the JS files directly. There is a way to debug the TypeScript source, but it involves some extra work. First, set up IIS express to serve up the project folder:

 

 

Then, edit the JS files to point to the localhost url:

 

 

Now, you can set a breakpoint in a TS file and it will be hit:

 

 

The disadvantage of this approach is that you must re-edit the JS files every time you recompile them.

 

The next demo involves functionality that is available in JSAPI 3.x, but not yet at 4.x. Namely, the ability to grab an image resource and display it as a layer. Here is a web page that displays the latest weather radar imagery:

 

 

The latest image is a fixed url, so nothing special needs to be done to reference it. Wouldn't it be cool, however, to display an animated loop of the 10 latest images? But there's a problem.

 

Let's add the LocalRadarLoop demo project code (attached) to the VS2017 solution and look at pageHelper.ts:

 

     export class myApp {
          public static readonly isExtension: boolean = false;
          public static readonly latestOnly: boolean = true;
     }

 

When isExtension is false, and latestOnly is true, the app behaves like the web page previously shown.

Note also this section of extension-only code that must be commented out for the app to run as a normal web page:

 

               // **** BEGIN Extension-only block ****
               /*
               if (myApp.isExtension) {
                    let sDefaultCode: string = defaultLocalCode;
                    chrome.storage.local.get({ localRadarCode: sDefaultCode },
                         (items: any) => {
                              let sCode: string = items.localRadarCode;
                              let sel: HTMLSelectElement = <HTMLSelectElement>document.getElementById("localRadarCode");
                              sel.value = sCode;
                              this.setRadar();
                         });
                    return;
               }
               */

               // **** END ****

 

Because the latest set of radar images do not have fixed names, it is necessary to obtain a directory listing to find out what they are. If you set latestOnly to false and run the app, however, you will run into the dreaded CORS policy error:

 

 

This is where the power of Chrome extensions comes into play. Set isExtension to true, and uncomment the extension-only code (which enables a saved user option), and load the app as an extension. Now you get the desired animation loop!

 

Note the relevant line in manifest.json which enables the XMLHttpRequest to run without a CORS error:

 

 

Now, as I pointed out earlier, Chrome extensions are distributed through Chrome Web Store:

 

 

There are some advantages to this. For example, updates are automatically distributed to users. You can also create an "invisible" store entry, or publish only to testers. I find that last feature useful for distributing an extension that I created for my personal use only. Other distribution options do exist, which you can read about at this link.

 

In conclusion, Chrome extensions enable pure client-side functionality that otherwise would not be possible without the aid of web services. Chrome Web Store provides a convenient way to distribute extensions and updates, with public and private options.

 

The Local Radar Loop extension is available free at Chrome Web Store.

Outcomes