<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Benedikt</title>
    <description>The latest articles on DEV Community by Benedikt (@derhackler).</description>
    <link>https://dev.to/derhackler</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F254059%2Fe14c13dc-5425-4307-8873-0473b5cd6333.jpeg</url>
      <title>DEV Community: Benedikt</title>
      <link>https://dev.to/derhackler</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/derhackler"/>
    <language>en</language>
    <item>
      <title>Walkthrough: The "Github Activity Indicator" Web Extension</title>
      <dc:creator>Benedikt</dc:creator>
      <pubDate>Wed, 23 Oct 2019 19:14:38 +0000</pubDate>
      <link>https://dev.to/derhackler/walkthrough-the-github-activity-indicator-web-extension-pj2</link>
      <guid>https://dev.to/derhackler/walkthrough-the-github-activity-indicator-web-extension-pj2</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;I like "Awesome XYZ" lists on Github. They often provide a fantastic summary of the most important libraries/topics in a dev ecosystem. Often enough though, the linked repositories are orphaned and no activity happend in years. I therefore wrote a little browser extension &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/github-activity-indicator/" rel="noopener noreferrer"&gt;for Firefox&lt;/a&gt; and &lt;a href="https://chrome.google.com/webstore/detail/github-activity-indicator/ppedpdikbacabbebipimhdcijkkdefkc?hl=en&amp;amp;authuser=0" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; that indicates since how many days a repository was not changed. It's free and open source - give it a try!&lt;/p&gt;

&lt;p&gt;In this article, I'd like to walk through &lt;a href="https://github.com/derhackler/githubactivityindicator" rel="noopener noreferrer"&gt;the source code&lt;/a&gt; of my first web extension and share my lessons learned.&lt;/p&gt;

&lt;h1&gt;
  
  
  Mozilla MDN is great
&lt;/h1&gt;

&lt;p&gt;The Mozilla &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions" rel="noopener noreferrer"&gt;MDN documentation&lt;/a&gt; is great. I'm a mediocre Javascript developer with zero experience in browser extension development. However the documentation contains almost everything you need to get up and running quickly.&lt;/p&gt;

&lt;h1&gt;
  
  
  File Organization
&lt;/h1&gt;

&lt;p&gt;The plugin consists of the following relevant files that I will discuss in this article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manifest.json&lt;/li&gt;
&lt;li&gt;addindicators.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Extension Manifest
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;manifest.json&lt;/code&gt; lives in the root folder of the extension and contains important meta-data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn3jwk1nqnw8w6ug7ngwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fn3jwk1nqnw8w6ug7ngwt.png" alt="Metadata in the manifest"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First I describe my extension and give it a nice icon. For the &lt;code&gt;version&lt;/code&gt; field it's recommended to use &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fve348642tss8v1mkh2y2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fve348642tss8v1mkh2y2.png" alt="browser settings and permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Firefox some additional settings are required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;id&lt;/code&gt; to a guid. Even though it is stated as optional in the documentation, you should do it. Otherwise you will run into issues later on (e.g. you can't access local storage)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;strict_min_version&lt;/code&gt; to something fairly recent. Otherwise you will get warnings when you try to access APIs that are not available in earlier versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that Chrome will complain that it does not know about this section. I just ignored this warning.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;permissions&lt;/code&gt; section you have to specify the permissions that the plugin requires. Plugins run with high priviledges so only ask for permissions you really need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I request access to all pages in the github api subdomain. You can then just use &lt;code&gt;fetch&lt;/code&gt; and CORS does not get in your way.&lt;/li&gt;
&lt;li&gt;I also want to access local storage, so that I can save the settings of the plugin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo2sppjh0ustt71of6oud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo2sppjh0ustt71of6oud.png" alt="plugin content"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Within &lt;code&gt;options_ui&lt;/code&gt; you define the html page that will be rendered as iframe in the settings page of the plugin. In my plugin it allows you to set a custom Github API access token.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;content_scripts&lt;/code&gt; allow you to inject custom Javascript into arbitrary pages: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;matches&lt;/code&gt; looks for URL patterns. My plugin should only be activated on github.com.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;js&lt;/code&gt; lists the Javascript files that are injected into these pages. &lt;code&gt;addindicators.js&lt;/code&gt; is the interesting one. I've included a &lt;a href="https://github.com/mozilla/webextension-polyfill" rel="noopener noreferrer"&gt;browser polyfill&lt;/a&gt; so that the web extension works on both Chrome and Firefox. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; The order of the scripts matter. First I included the polyfill after my script and it took me a while to figure out why it's not working.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;addindicators.js&lt;/code&gt; contains the implementation of the plugin. The general flow is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable or disable debug logs&lt;/li&gt;
&lt;li&gt;Get the API key from the settings or use the default.&lt;/li&gt;
&lt;li&gt;Find all links to Github repositories&lt;/li&gt;
&lt;li&gt;Assemble a GraphQl query and query Github&lt;/li&gt;
&lt;li&gt;Add an indicator to each repository link&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at some code:&lt;/p&gt;

&lt;h2&gt;
  
  
  Debug Flags
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F26ezwdu9j7kb9j0f2vjy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F26ezwdu9j7kb9j0f2vjy.png" alt="Code to enable debugging"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I check if the debug flag is set in the browser local storage and set a global variable accordingly. All &lt;code&gt;dlog&lt;/code&gt; code you see later just checks on the &lt;code&gt;debugEnabled&lt;/code&gt; flag and either prints the message to the console or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned&lt;/strong&gt;: &lt;code&gt;browser&lt;/code&gt; will only work in Firefox unless you use the polyfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the API token
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo83i41fm7jj65hnf6nbg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fo83i41fm7jj65hnf6nbg.png" alt="Code to get the Github API token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It took me a surprising amount of code to either get the token from local storage (set via the settings dialog), or use the default token. Improvement suggestions are very welcome!&lt;/p&gt;

&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use &lt;code&gt;browser.storage.sync&lt;/code&gt; instead of &lt;code&gt;browser.storage.local&lt;/code&gt;. This ensures that the token is synced across user devices (if enabled).&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;isTokenValid&lt;/code&gt; function I also check for the string &lt;em&gt;'Undefined'&lt;/em&gt;. I need to do this because I was lazy in my settings dialog implementation...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SECURITY REMARK:&lt;/strong&gt; I created an unpriviledged Github token for my account that I use as default token. In general this is a horrible security sin so please &lt;strong&gt;don't do this at home&lt;/strong&gt;. I just couldn't quickly find a more convenient way without doing the lovely OAuth dance.&lt;/li&gt;
&lt;li&gt;My function naming is neither consistent nor ideomatic throughout the whole codebase. Sorry for that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Find links to Github repositories
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyy8c98xmpk67up6wswvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fyy8c98xmpk67up6wswvb.png" alt="find links to github repositories"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First I get all links on the page via &lt;code&gt;document.getElementsByTagName&lt;/code&gt; and then filter out all links that are not links to github repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned:&lt;/strong&gt; At the first glance &lt;code&gt;document.getElementsByTagName&lt;/code&gt; seems to return an array. You can also iterate over it with a for loop like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for(const x of document.getElementsByTagName('a')){
    // do something
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However it's not a real array thats why this would fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this will fail!!
document.getElementsByTagName('a').map(x =&amp;gt; x.href())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to use &lt;code&gt;Array.from&lt;/code&gt; to change it into an array.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6dx4svklniy5r2fdttrr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F6dx4svklniy5r2fdttrr.png" alt="helper functions to filter only github links"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;maybe_repo_link&lt;/code&gt; I filter out github urls that match the &lt;em&gt;owner/repository&lt;/em&gt; pattern.&lt;/p&gt;

&lt;p&gt;Many Github internal links match the same pattern, so I wrote &lt;code&gt;is_repository&lt;/code&gt; to catch at least a few more and avoid errors later on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lessons learned:&lt;/strong&gt; At first I tried to filter the internal URLs like that and was annoyed that it didn't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this does not work!
norepo.forEach(x =&amp;gt; {
    if(mayberepo.startsWith(x)){
        return false;
    }
});

return true;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As very clearly described in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach" rel="noopener noreferrer"&gt;MDN Documentation&lt;/a&gt;, &lt;code&gt;forEach&lt;/code&gt; is useful if you're interested in triggering side effects. However you can't break the loop. My return statement was just ignored and the function always returned &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying Github via GraphQL
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; is a language for accessing your APIs. Compared to REST, you have more flexibility in defining what you want to query in a single call and which data should be included in the response.&lt;/p&gt;

&lt;p&gt;I make use of the &lt;a href="https://developer.github.com/v4/" rel="noopener noreferrer"&gt;Github GraphQL API&lt;/a&gt; because it only takes a single HTTP call to query the information for all repositories on a page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhg10v1zt62sdv521s62g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fhg10v1zt62sdv521s62g.png" alt="GraphQL Flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First I assemble the query, then I query Github, and finally I extract the information from the response.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fh0fix2vlutmf47rhtxbi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fh0fix2vlutmf47rhtxbi.png" alt="Building the GraphQL query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use a single GraphQL query to fetch the last repository push date of all repositories at once.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;graphql_fragment_for&lt;/code&gt; I assemble the query for a single repository. In order to have multiple &lt;code&gt;repository&lt;/code&gt; clauses, I need to prefix them by an arbitrary unique name.&lt;/p&gt;

&lt;p&gt;Note the &lt;code&gt;...fields&lt;/code&gt; section: this states that at this position the fragment with the name "fields" should be inserted. Fragments are a little bit like makros and avoid repetition.&lt;/p&gt;

&lt;p&gt;The response of the function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r0: repository(owner:"derhackler",name:"catexcel"){
    ...fields
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SECURITY_ALERT:&lt;/strong&gt; I use string concatenation to pass in &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;. THIS IS DANGEROUS and in general a very bad idea. Think of SQL Injection but only for your API. GraphQL supports the concept of &lt;a href="https://graphql.org/learn/queries/#variables" rel="noopener noreferrer"&gt;variables&lt;/a&gt; which should be used instead. I couldn't figure out though on how to use them in my scenario... &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;graphql_for_repos&lt;/code&gt;, I assemble the subqueries for the repositories. In addition I ask for how many API calls I have left (for debugging purposes).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxaeglow1e7b81weof1er.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fxaeglow1e7b81weof1er.png" alt="Querying the Github GraphQl API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The actual query is an HTTP POST to the Graphql endpoint of Github. Compared to REST, GraphQL APIs are exposed via a single URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F04jrmm5ol42nsd0dj78k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F04jrmm5ol42nsd0dj78k.png" alt="Extracting the data from the response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbueeqvtswhdndjr7vrmn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbueeqvtswhdndjr7vrmn.png" alt="Converting the timestamp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;extract_from_graph&lt;/code&gt; I filter out all responses where no repository data could be found (and just ignore it) and convert the timestamp to days until &lt;em&gt;now&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Augmenting the Repository Links
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbpwoml8ejf0gq8509ona.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fbpwoml8ejf0gq8509ona.png" alt="Augment DOM links"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the last step, I add a little indicator to each repository link. I'm not using the DOM elements that I queried orignially, but query for each link individually again. I think this is cleaner as the original element may have been gone already.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Writing browser extensions is fun and surprisingly painless&lt;/li&gt;
&lt;li&gt;Writing blogposts reviewing my own code takes me much longer than writing the code&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webextension</category>
      <category>javascript</category>
      <category>walkthrough</category>
    </item>
  </channel>
</rss>
