{"action":"created","comment":{"url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/comments/208523913","pull_request_review_id":144348020,"id":208523913,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwODUyMzkxMw==","diff_hunk":"@@ -0,0 +1,553 @@\n+---\n+layout: article\n+section: Blog\n+title: \"From SPA to PWA\"\n+author: \"Marco Otte-Witte\"\n+github-handle: marcoow\n+twitter-handle: marcoow\n+---\n+\n+Progressive Web Apps are the next level of browser based applications. While\n+Single Page Apps (SPAs) already meant a giant leap forward for web apps, PWAs\n+are taking things even one step further. They offer a rich user experience that\n+parallels what users know and expect from native apps and combine that with the\n+benefits that browser based applications provide. In this post, we'll look at\n+how to turn a Single Page App into a Progressive Web App.\n+\n+<!--break-->\n+\n+#### Breethe\n+\n+Throughout this post, we will use [Breethe](https://breethe.app) as an example.\n+Breethe is a Progressive Web App that gives users quick and easy access to air\n+quality data for locations around the world. Pollution and global warming are\n+getting worse rather than better and having easy access to data that shows how\n+bad the situation actually is, is the first step for everyone to question their\n+decisions and maybe change a few things in their daily lives.\n+\n+![Video of the Breethe PWA](/images/posts/2018-07-24-from-spa-to-pwa/breethe-video.gif)\n+\n+The application is [open source](https://github.com/simplabs/breethe-client)\n+and we encourage everyone interested to look through the source for reference.\n+To learn more about how we built Breethe with Glimmer.js, refer to the [previous\n+post in this series](/blog/2018/07/03/building-a-pwa-with-glimmer-js.html).\n+None of the contents in this post are specific to Glimmer.js in any way though,\n+but all generally applicable to all frontend stacks.\n+\n+#### Progressive Web Apps\n+\n+In short, Progressive Web Apps are websites that look and behave like native\n+apps. While the idea of building apps with web technologies has been around for\n+a long time, it never really took off until recently. In fact, Steve Jobs\n+himself introduced the idea at WWDC 2007, just shortly after the unveiling of\n+the orignal iPhone:\n+\n+<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/y1B2c3ZD9fk?start=4451\" frameborder=\"0\" allow=\"autoplay; encrypted-media\" allowfullscreen class=\"video\"></iframe>\n+\n+Unfortunately that idea was one of the few of Jobs' that never really took off.\n+In fact, Apple released the first native SDK for the iPhone in 2008. That\n+change in strategy lead to a huge market for native mobile apps and played a\n+significant role in mobile devices becoming ever more ubiquitous over the past\n+decade\n+([not always to the benefit of everyone](https://en.wikipedia.org/wiki/Mobile_phone_overuse)\n+as is becoming more and more obvious recently).\n+\n+While native apps can enable great things and previously unforeseen use cases,\n+they have some drawbacks. They generally\n+[show high conversion rates](https://jmango360.com/wiki/mobile-app-vs-mobile-website-statistics/)\n+which is the main reason why marketing departments keep pushing for them. On\n+the downside though, they have some significant drawbacks, the main one being\n+that apps have to be downloaded and installed which can be a significant effort\n+for users for several reasons (ultimately leading to the question of how many\n+potential users are lost before they even install the app where they then\n+**could** have been successfully converted):\n+\n+* users that are on the mobile website already, when asked to install the app,\n+  need to leave that website, go to the app store, download the app and\n+  continue through the installation process\n+* once the app is installed, all input that potentially was made on the website\n+  before is lost and needs to be restored besides users potentially needing to\n+  login in the app again\n+* native apps are\n+  [relatively large on average](https://sweetpricing.com/blog/2017/02/average-app-file-size/),\n+  leading to long download times which is particularly painful on often spotty\n+  mobile connections\n+\n+In contrast, web apps are easily accessible via the browser without forcing\n+users to go through an app store and installation process and load almost\n+instantaneously - if done right, even on spotty mobile connections.\n+\n+Another advantage of native apps is mostly a historic one now. Historically,\n+apps were able to offer a better user experience both in terms of the purely\n+visual experience and also in terms of features. With the quickly evolving web\n+platform and capabilities of modern browsers, this is no longer the case though\n+and web apps are now capable of offering equal if not better user experiences\n+compared to native apps. Combined with almost instantaneous load times and\n+superior discoverability, Progressive Web Apps are clearly the better choice\n+than native apps for the vast majority of use cases now.\n+\n+This has only been the case relatively recently though. While Chrome and\n+Firefox supported Service Workers (which are the main building block for PWAs)\n+for quite some time, two major browsers were falling behind - namely Safari and\n+Internet Explorer. These two (actually not Internet Explorer but its successor\n+Edge) have finally caught up recently and\n+[Service Workers are now supported by all major browsers](https://caniuse.com/#feat=serviceworkers),\n+making Progressive Web Apps a viable alternative to native apps for most\n+businesses with ca. 84% of the global user base on browsers and OSes capable of\n+running PWAs as of July 2018.\n+\n+A decade after the introduction of the original iPhone (and a detour via the\n+[multi-billion native app economy](https://www.appannie.com/en/insights/market-data/predictions-app-economy-2018/)),\n+Progressive Web Apps are ready to be used and they are here to stay. And we are\n+only getting started - as Progressive Web Apps have only recently really took\n+off, it's fair to expect massive improvements in terms of what's possible over\n+the next coming years.\n+\n+#### What are PWAs?\n+\n+Progressive Web Apps have some distinct characteristics, the main ones being:\n+\n+* Progressiveness: they work for every user, regardless of their device or\n+  browser of choice; depending on the capabilities of that environment, they\n+  will enable more or less of their functionality\n+* Responsiveness: they fit any form factor and screen sizes\n+* Connectivity Independence: they work on low quality networks or without any\n+  network at all (and thus fully offline)\n+* App-likeliness: they offer the rich user experience that users know and love\n+  from native apps\n+* Installability: they can be installed on the user's home screen without\n+  having to go through an app store\n+\n+We will be focussing on 2 of these characteristics in this article -\n+Installability and Connectivity Independence.\n+\n+#### Installability\n+\n+Progressive Web apps can be installed on the user's home screen and thus\n+_\"taken out of the browser\"_ so that they mingle with the user's native apps\n+without the user noticing a difference. That characteristic is enabled by the\n+[_\"App Manifest\"_](https://developers.google.com/web/fundamentals/web-app-manifest/)\n+that describes how an app is supposed to behave when run _\"outside\"_ of the\n+browser. The App Manifest is a simple JSON file with key/value pairs that\n+configure the main aspects of the app. The App Manifest for Breethe looks like\n+this:\n+\n+```json\n+{\n+  \"name\": \"Breethe\",\n+  \"short_name\": \"Breethe\",\n+  \"description\":\"Air Quality Data for Locations around the World\",\n+  \"theme_color\": \"#002eb8\",\n+  \"background_color\": \"#002eb8\",\n+  \"display\": \"standalone\",\n+  \"orientation\": \"portrait\",\n+  \"start_url\": \"/\",\n+  \"icons\": [\n+    {\n+      \"src\": \"images/app-images/192x192.png\",\n+      \"sizes\": \"192x192\",\n+      \"type\": \"image/png\"\n+    },\n+    {\n+      \"src\": \"images/app-images/512x512.png\",\n+      \"sizes\": \"512x512\",\n+      \"type\": \"image/png\"\n+    }\n+  ],\n+  \"splash_pages\": null\n+}\n+```\n+\n+The manifest is presented to the browser via a `meta` tag:\n+\n+```html\n+<link rel=\"manifest\" href=\"/manifest.webmanifest\">\n+```\n+\n+The main keys in the manifest are `name`, `icons`, `background_color`,\n+`start_url`, `display` and `orientation`:\n+\n+* `name`: the name of the app that will be shown on the user's home screen once\n+  the app is installed\n+* `icons`: icons in various sizes to use for the app on the home screen, the\n+  task switcher and elsewhere\n+* `background_color`: sets the color for the splash screen that is shown when\n+  the app is started from the home screen\n+* `start_url`: the URL to load when the app is started from the home screen\n+* `display`: tells the browser how to display the app when started from the\n+  home screen; this should usually be `\"standalone\"`\n+* `orientation`: the orientation to launch the app with if only one orientation\n+  is supported or makes sense for the app\n+\n+Breethe, when installed to the user's home screen on iOS shows up like this:\n+\n+![The Breethe app icon on the home screen](/images/posts/2018-07-24-from-spa-to-pwa/breethe-app-icon.png)\n+\n+When launched from the home screen, it starts up like a native app, without any\n+browser UI:\n+\n+![The Breethe PWA starting up standalone](/images/posts/2018-07-24-from-spa-to-pwa/breethe-startup-standalone.gif)\n+\n+#### Connectivity Independence\n+\n+The main requirement for any web application to be able to work independently\n+of the network is that it must be able to start up while there is no network at\n+all. For an application that runs in a browser that is essentially made for\n+loading anything that it requires remotely, that is no easy task. One of the\n+first approaches for achieving just this, was\n+[Google Gears](https://en.wikipedia.org/wiki/Gears_(software)). Gears was\n+released as early as 2007 as a proprietary extension for Chrome and allowed\n+offline usage of e.g. Gmail. Gears never really took off in the broader\n+ecosystem though and was discontinued in 2010.\n+\n+Many of the orignal ideas behind Gears found their way into the HTML5 spec\n+though, in particular the idea of Gear's `LocalServer` module that allowed\n+running a local server inside of the browser. That server would be able to\n+handle any requests made by the browser instead of actually sending these\n+request over the network. This is essentially (besides some other features)\n+what service workers are. They are standalone pieces of JavaScript code that\n+gets installed into the users browser and are subsequently able to intercept\n+any request that the browser makes and serve the response from their internal\n+cache instead of relying on the remote server's response.\n+\n+Installing a service worker is as simple as running a little script:\n+\n+```html {% raw %}\n+<script>\n+  if ('serviceWorker' in navigator) {\n+    navigator.serviceWorker.register('/service-worker.js').catch(function() {\n+      // handle errors\n+    });\n+  }\n+</script>\n+{% endraw %}```\n+\n+This calls the navigator's `serviceWorker` API's `register` method (if the\n+browser supports that API), passing the name of the JavaScript file with the\n+service worker code as an argument. The `register` method returns a promise\n+that rejects if the service worker could not successfully be registered.\n+\n+Once the service worker is registered, it will start receiving various events\n+during the\n+[service worker lifecycle](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Basic_architecture).\n+The first event in that lifecycle is the _\"install\"_ event which is typically\n+used to pre-cache resources and thus make them available for later offline use.\n+In the Breethe PWA, we use that event to preload all of the app's essential\n+resources:\n+\n+```js\n+const CACHE_NAME = 'breethe-cache-v1';\n+const PRE_CACHED_ASSETS = [\n+  '/app.js',\n+  '/app.css',\n+  '/index.html'\n+];\n+\n+self.addEventListener('install', function(event) {\n+  event.waitUntil(\n+    caches.open(CACHE_NAME).then(function(cache) {\n+      let cachePromises = PRE_CACHED_ASSETS.map(function(asset) {\n+        var url = new URL(asset, location.href);\n+        var request = new Request(url);\n+        return fetch(request).then(function(response) {\n+          return cache.put(asset, response);\n+        });\n+      });\n+\n+      return Promise.all(cachePromises);\n+    })\n+  );\n+});\n+```\n+\n+Here, when the _\"install\"_ event is fired, we open the service worker cache,\n+request the critical assets and store them in the service worker's cache.\n+\n+The next event in the lifecycle is the _\"activate\"_ event that is typically\n+used to delete old caches so that when the service worker is updated, no stale\n+data is left on the user's device. In the Breethe PWA, we only use one cache\n+and when the service worker is activated, simply delete all caches that are not\n+that current one:\n+\n+```js\n+self.addEventListener('activate', function(event) {\n+  event.waitUntil(\n+    caches.keys().then(function(cacheNames) {\n+      return Promise.all(\n+        // delete old caches\n+        cacheNames.map(function(cacheName) {\n+          if (cacheName !== CACHE_NAME) {\n+            return caches.delete(cacheName);\n+          }\n+        })\n+      );\n+    })\n+  );\n+});\n+```\n+\n+After the service worker is installed and activated, it starts receiving\n+_\"fetch\"_ events whenever the browser makes a request for any remote resource.\n+This is the relevant event that we need to listen for in order to make sure the\n+PWA can start up successfully if the network is down. In the case of an SPA\n+where there only is one HTML document that is `index.html`, this is as easy as\n+always serving that when the original request fails:\n+\n+```js\n+self.addEventListener('fetch', function(event) {\n+  if (event.request.headers.get('accept').startsWith('text/html')) {\n+    event.respondWith(\n+      fetch(event.request).catch(error => {\n+        return caches.match('index.html');\n+      })\n+    );\n+  }\n+});\n+```\n+\n+Here, we first check whether the request is an HTML request (in reality in\n+Breethe,\n+[we check a few other things as well](https://github.com/simplabs/breethe-client/blob/master/lib/service-worker/workers/service-worker.js#L88))\n+and if that is the case, try making the request and loading the resource from\n+the network first. If that fails and the promise returned from `fetch` rejects\n+(one possible reason for that being that the device is offline), we simply\n+serve the `index.html` from the service worker's cache so that the app can\n+start up successfully in the browser. All of the application's assets that are\n+referenced in `index.html` are cached to and served from the service worker's\n+cache as well in a\n+[separate event handler](https://github.com/simplabs/breethe-client/blob/master/lib/service-worker/workers/service-worker.js#L65).\n+\n+Allowing apps to start up offline with Service Workers is very straight forward\n+and does not even require implementing a whole lot of logic -\n+[Breethe's service worker](https://github.com/simplabs/breethe-client/blob/master/lib/service-worker/workers/service-worker.js)\n+does not even have 100 lines of code. Of course being able to start up the app\n+while offline only solves half of the problem though when all of the app's data\n+is loaded from a remote API and thus would be unavailable when offline.\n+\n+##### Offline Data\n+\n+The web platform provides a number of APIs for storing data in the browser and\n+thus making it available for offline use. The\n+[WebStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)\n+defines `localStorage` and `sessionStorage` which can be used for storing\n+key/value pairs. Cookies have been around for quite some time and cannot only\n+be used for tracking users or keeping their sessions alive but also for storing\n+simple data. When dealing with bigger, structured data sets though, the storage\n+API of choice is generally\n+[`IndexedDB`](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API).\n+\n+`IndexedDB` is a transactional database system, similar to SQL-based RDBMS:\n+\n+```js\n+let openRequest = window.indexedDB.open('MyTestDatabase');\n+openRequest.onsuccess = function(event) {\n+  let db = event.target.result;\n+  var transaction = db.transaction(['locations']);\n+  var objectStore = transaction.objectStore('locations');\n+  var request = objectStore.get('1');\n+  request.onsuccess = function(event) {","path":"blog/_posts/2018-07-24-from-spa-to-pwa.md","position":355,"original_position":348,"commit_id":"7e673095673edc0d3999ce035a9bfecead7a5371","original_commit_id":"5f66971bd03d699fec42f3200e068cda3ab43185","user":{"login":"marcoow","id":1510,"node_id":"MDQ6VXNlcjE1MTA=","avatar_url":"https://avatars1.githubusercontent.com/u/1510?v=4","gravatar_id":"","url":"https://api.github.com/users/marcoow","html_url":"https://github.com/marcoow","followers_url":"https://api.github.com/users/marcoow/followers","following_url":"https://api.github.com/users/marcoow/following{/other_user}","gists_url":"https://api.github.com/users/marcoow/gists{/gist_id}","starred_url":"https://api.github.com/users/marcoow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcoow/subscriptions","organizations_url":"https://api.github.com/users/marcoow/orgs","repos_url":"https://api.github.com/users/marcoow/repos","events_url":"https://api.github.com/users/marcoow/events{/privacy}","received_events_url":"https://api.github.com/users/marcoow/received_events","type":"User","site_admin":false},"body":"Yes, good catch 👍 ","created_at":"2018-08-08T09:58:50Z","updated_at":"2018-08-08T09:58:50Z","html_url":"https://github.com/simplabs/simplabs.github.io/pull/212#discussion_r208523913","pull_request_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/comments/208523913"},"html":{"href":"https://github.com/simplabs/simplabs.github.io/pull/212#discussion_r208523913"},"pull_request":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212"}},"in_reply_to_id":208269264},"pull_request":{"url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212","id":205857861,"node_id":"MDExOlB1bGxSZXF1ZXN0MjA1ODU3ODYx","html_url":"https://github.com/simplabs/simplabs.github.io/pull/212","diff_url":"https://github.com/simplabs/simplabs.github.io/pull/212.diff","patch_url":"https://github.com/simplabs/simplabs.github.io/pull/212.patch","issue_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/212","number":212,"state":"open","locked":false,"title":"[WIP] From SPA to PWA","user":{"login":"marcoow","id":1510,"node_id":"MDQ6VXNlcjE1MTA=","avatar_url":"https://avatars1.githubusercontent.com/u/1510?v=4","gravatar_id":"","url":"https://api.github.com/users/marcoow","html_url":"https://github.com/marcoow","followers_url":"https://api.github.com/users/marcoow/followers","following_url":"https://api.github.com/users/marcoow/following{/other_user}","gists_url":"https://api.github.com/users/marcoow/gists{/gist_id}","starred_url":"https://api.github.com/users/marcoow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/marcoow/subscriptions","organizations_url":"https://api.github.com/users/marcoow/orgs","repos_url":"https://api.github.com/users/marcoow/repos","events_url":"https://api.github.com/users/marcoow/events{/privacy}","received_events_url":"https://api.github.com/users/marcoow/received_events","type":"User","site_admin":false},"body":"This is the second part in the blog post serices on Breethe, covering PWAs and offline functionality (Web Manifest, Service Workers, offline data with `IndexedDB` and Orbit, testing).","created_at":"2018-08-02T20:52:07Z","updated_at":"2018-08-08T09:58:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7fbf4abd385909af2596593f234247c921dd4711","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":336126252,"node_id":"MDU6TGFiZWwzMzYxMjYyNTI=","url":"https://api.github.com/repos/simplabs/simplabs.github.io/labels/blog%20post","name":"blog post","color":"fbca04","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212/commits","review_comments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212/comments","review_comment_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/212/comments","statuses_url":"https://api.github.com/repos/simplabs/simplabs.github.io/statuses/7e673095673edc0d3999ce035a9bfecead7a5371","head":{"label":"simplabs:from-spa-to-pwa","ref":"from-spa-to-pwa","sha":"7e673095673edc0d3999ce035a9bfecead7a5371","user":{"login":"simplabs","id":56627,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU2NjI3","avatar_url":"https://avatars3.githubusercontent.com/u/56627?v=4","gravatar_id":"","url":"https://api.github.com/users/simplabs","html_url":"https://github.com/simplabs","followers_url":"https://api.github.com/users/simplabs/followers","following_url":"https://api.github.com/users/simplabs/following{/other_user}","gists_url":"https://api.github.com/users/simplabs/gists{/gist_id}","starred_url":"https://api.github.com/users/simplabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simplabs/subscriptions","organizations_url":"https://api.github.com/users/simplabs/orgs","repos_url":"https://api.github.com/users/simplabs/repos","events_url":"https://api.github.com/users/simplabs/events{/privacy}","received_events_url":"https://api.github.com/users/simplabs/received_events","type":"Organization","site_admin":false},"repo":{"id":10031701,"node_id":"MDEwOlJlcG9zaXRvcnkxMDAzMTcwMQ==","name":"simplabs.github.io","full_name":"simplabs/simplabs.github.io","owner":{"login":"simplabs","id":56627,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU2NjI3","avatar_url":"https://avatars3.githubusercontent.com/u/56627?v=4","gravatar_id":"","url":"https://api.github.com/users/simplabs","html_url":"https://github.com/simplabs","followers_url":"https://api.github.com/users/simplabs/followers","following_url":"https://api.github.com/users/simplabs/following{/other_user}","gists_url":"https://api.github.com/users/simplabs/gists{/gist_id}","starred_url":"https://api.github.com/users/simplabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simplabs/subscriptions","organizations_url":"https://api.github.com/users/simplabs/orgs","repos_url":"https://api.github.com/users/simplabs/repos","events_url":"https://api.github.com/users/simplabs/events{/privacy}","received_events_url":"https://api.github.com/users/simplabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/simplabs/simplabs.github.io","description":"The source code for http://simplabs.com","fork":false,"url":"https://api.github.com/repos/simplabs/simplabs.github.io","forks_url":"https://api.github.com/repos/simplabs/simplabs.github.io/forks","keys_url":"https://api.github.com/repos/simplabs/simplabs.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/simplabs/simplabs.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/simplabs/simplabs.github.io/teams","hooks_url":"https://api.github.com/repos/simplabs/simplabs.github.io/hooks","issue_events_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/simplabs/simplabs.github.io/events","assignees_url":"https://api.github.com/repos/simplabs/simplabs.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/simplabs/simplabs.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/simplabs/simplabs.github.io/tags","blobs_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/simplabs/simplabs.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/simplabs/simplabs.github.io/languages","stargazers_url":"https://api.github.com/repos/simplabs/simplabs.github.io/stargazers","contributors_url":"https://api.github.com/repos/simplabs/simplabs.github.io/contributors","subscribers_url":"https://api.github.com/repos/simplabs/simplabs.github.io/subscribers","subscription_url":"https://api.github.com/repos/simplabs/simplabs.github.io/subscription","commits_url":"https://api.github.com/repos/simplabs/simplabs.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/simplabs/simplabs.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/simplabs/simplabs.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/simplabs/simplabs.github.io/merges","archive_url":"https://api.github.com/repos/simplabs/simplabs.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/simplabs/simplabs.github.io/downloads","issues_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/simplabs/simplabs.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/simplabs/simplabs.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/simplabs/simplabs.github.io/labels{/name}","releases_url":"https://api.github.com/repos/simplabs/simplabs.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/deployments","created_at":"2013-05-13T12:59:23Z","updated_at":"2018-07-31T14:59:29Z","pushed_at":"2018-08-07T20:54:10Z","git_url":"git://github.com/simplabs/simplabs.github.io.git","ssh_url":"git@github.com:simplabs/simplabs.github.io.git","clone_url":"https://github.com/simplabs/simplabs.github.io.git","svn_url":"https://github.com/simplabs/simplabs.github.io","homepage":"","size":54103,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":5,"mirror_url":null,"archived":false,"open_issues_count":16,"license":null,"forks":5,"open_issues":16,"watchers":1,"default_branch":"master"}},"base":{"label":"simplabs:master","ref":"master","sha":"0a4ea149631f23ca33b9ff31b9a51c8c32a3c688","user":{"login":"simplabs","id":56627,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU2NjI3","avatar_url":"https://avatars3.githubusercontent.com/u/56627?v=4","gravatar_id":"","url":"https://api.github.com/users/simplabs","html_url":"https://github.com/simplabs","followers_url":"https://api.github.com/users/simplabs/followers","following_url":"https://api.github.com/users/simplabs/following{/other_user}","gists_url":"https://api.github.com/users/simplabs/gists{/gist_id}","starred_url":"https://api.github.com/users/simplabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simplabs/subscriptions","organizations_url":"https://api.github.com/users/simplabs/orgs","repos_url":"https://api.github.com/users/simplabs/repos","events_url":"https://api.github.com/users/simplabs/events{/privacy}","received_events_url":"https://api.github.com/users/simplabs/received_events","type":"Organization","site_admin":false},"repo":{"id":10031701,"node_id":"MDEwOlJlcG9zaXRvcnkxMDAzMTcwMQ==","name":"simplabs.github.io","full_name":"simplabs/simplabs.github.io","owner":{"login":"simplabs","id":56627,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU2NjI3","avatar_url":"https://avatars3.githubusercontent.com/u/56627?v=4","gravatar_id":"","url":"https://api.github.com/users/simplabs","html_url":"https://github.com/simplabs","followers_url":"https://api.github.com/users/simplabs/followers","following_url":"https://api.github.com/users/simplabs/following{/other_user}","gists_url":"https://api.github.com/users/simplabs/gists{/gist_id}","starred_url":"https://api.github.com/users/simplabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/simplabs/subscriptions","organizations_url":"https://api.github.com/users/simplabs/orgs","repos_url":"https://api.github.com/users/simplabs/repos","events_url":"https://api.github.com/users/simplabs/events{/privacy}","received_events_url":"https://api.github.com/users/simplabs/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/simplabs/simplabs.github.io","description":"The source code for http://simplabs.com","fork":false,"url":"https://api.github.com/repos/simplabs/simplabs.github.io","forks_url":"https://api.github.com/repos/simplabs/simplabs.github.io/forks","keys_url":"https://api.github.com/repos/simplabs/simplabs.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/simplabs/simplabs.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/simplabs/simplabs.github.io/teams","hooks_url":"https://api.github.com/repos/simplabs/simplabs.github.io/hooks","issue_events_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/simplabs/simplabs.github.io/events","assignees_url":"https://api.github.com/repos/simplabs/simplabs.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/simplabs/simplabs.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/simplabs/simplabs.github.io/tags","blobs_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/simplabs/simplabs.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/simplabs/simplabs.github.io/languages","stargazers_url":"https://api.github.com/repos/simplabs/simplabs.github.io/stargazers","contributors_url":"https://api.github.com/repos/simplabs/simplabs.github.io/contributors","subscribers_url":"https://api.github.com/repos/simplabs/simplabs.github.io/subscribers","subscription_url":"https://api.github.com/repos/simplabs/simplabs.github.io/subscription","commits_url":"https://api.github.com/repos/simplabs/simplabs.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/simplabs/simplabs.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/simplabs/simplabs.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/simplabs/simplabs.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/simplabs/simplabs.github.io/merges","archive_url":"https://api.github.com/repos/simplabs/simplabs.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/simplabs/simplabs.github.io/downloads","issues_url":"https://api.github.com/repos/simplabs/simplabs.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/simplabs/simplabs.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/simplabs/simplabs.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/simplabs/simplabs.github.io/labels{/name}","releases_url":"https://api.github.com/repos/simplabs/simplabs.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/simplabs/simplabs.github.io/deployments","created_at":"2013-05-13T12:59:23Z","updated_at":"2018-07-31T14:59:29Z","pushed_at":"2018-08-07T20:54:10Z","git_url":"git://github.com/simplabs/simplabs.github.io.git","ssh_url":"git@github.com:simplabs/simplabs.github.io.git","clone_url":"https://github.com/simplabs/simplabs.github.io.git","svn_url":"https://github.com/simplabs/simplabs.github.io","homepage":"","size":54103,"stargazers_count":1,"watchers_count":1,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":5,"mirror_url":null,"archived":false,"open_issues_count":16,"license":null,"forks":5,"open_issues":16,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212"},"html":{"href":"https://github.com/simplabs/simplabs.github.io/pull/212"},"issue":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/212"},"comments":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/issues/212/comments"},"review_comments":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212/comments"},"review_comment":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/pulls/212/commits"},"statuses":{"href":"https://api.github.com/repos/simplabs/simplabs.github.io/statuses/7e673095673edc0d3999ce035a9bfecead7a5371"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"marcoow"}}