AngularJS: Directive for checking # of watchers in scope

Performance is key in web development. Many developers forget this since there machines usually are more powerful then the average user machine. This is really important to take into account when building client side applications. In AngularJS the ng-repeat can be a source of bad performance, it’s super convenient to use but can create both digest loops and performance issues. Most of the time the performance can improve if you cut back on the two-way bindings. Every two-way binding creates a watcher that fires if something is changed, since Angular dosen’t know what changed it checks every watcher in the current scope of the change.

So if you are only displaying data with ng-repeat without any editing function there is no reason to use a two-way binding like {{whateverdata}}, instead you should use a one way binding like {{::whateverdata}}. This approach will save the number of watchers. When I develop in Angular I use a simple directive to double check my scopes for the number of watchers created. It gives me quick access to the figure and I can see if changes to the application affects the number of watchers.

[js]
.directive(‘scopewatchers’, function($timeout) {
return {
restrict: ‘AE’,
replace: ‘true’,
template: ‘<h2 ng-click="wcount()"># of watchers (click to update):{{watchers || "0"}}</h2>’,
controller: function($scope, $element) {
$scope.wcount = function() {
$timeout(function() {
$scope.watchers = $scope.watchersContainedIn($scope);
});
};

$scope.watchersContainedIn = function(scope) {
var watchers = (scope.$$watchers) ? scope.$$watchers.length : 0;
var child = scope.$$childHead;
while (child) {
watchers += (child.$$watchers) ? child.$$watchers.length : 0;
child = child.$$nextSibling;
}
return watchers;
};
}
};
})
[/js]

Then I can easaly pop it into any scoop I want to check the number of watchers currently active in that scope.

[html]
<scopewatchers></scopewatchers>
[/html]

I put together a Plunker demo of this built on the Digest Loop Demo I did a while back. With the amount of data in this demo it doesn’t really do any difference if I have 500+ watchers or 2 watchers but the principal is the same. For larger datasets it can be a huge difference when ng-repeat inserts new items to the DOM or changes order of the items.

https://plnkr.co/edit/SlBYfp?p=info

Raspberry PI: Central kodi database

When you run more then one Kodi device with a shared media repository you should also run a shared library. With a shared library you only need to update the library on one of the Kodi devices when new media is added. There are a few other great benefits of running a shared library like the ability to stop media on one device and continue watching on another. It will also show what has been watched regardless of which device you actually watched it on. I run OpenELEC on Raspberry Pi’s for all my media stations so this guide is focused on a Raspberry Pi implementation but the principal is the same for all devices capable of running Kodi and MySQL.

Preparations

Download the latest raspian image from https://www.raspberrypi.org/downloads/raspbian/ and put it on an sd-card with Win32DiskImager. Then put the sd-card in the Raspberry Pi and power it on. You can connect a monitor, mouse and keyboard for the initial setup. I usually just wait for it to boot up and check my routers DHCP list for it to show up and connect to it over SSH. This article is based on SSH terminal access but you can do the same on the Pi with a connected monitor.

Basic setup of the Raspberry Pi

First we need to configure the Raspberry Pi. Connect over SSH and login as pi with password raspberry. We want to:

[bash]sudo raspi-config[/bash]

  • Expand Filesystem – Expand the pi file system to use the entire SD-card.
  • Change User Password – Change the default raspberry password for the pi user.
  • Boot Options – Select B1 Console – Text console, requiring user to login
  • Advanced – Some additional settings
    • Hostname – Select a hostname for your pi. I user PISRV
    • Memory Split – Set to 16 since we will not use a monitor or run any other graphics on this machine.
  • Finish – Exit the raspi-config and reboot.

When the pi has booted up again we can login with our new password we configured. When in the console run:

[bash]sudo apt-get update[/bash]

Update the package lists from all the repositories.

[bash]sudo apt-get dist-upgrade[/bash]

Upgrade all installed packages.

Install MySQL

Install MySQL server. It will ask you to select a root password.

[bash]sudo apt-get install mysql-server[/bash]

We need to be able to access the MySQL server from other locations then localhost. Open up the MySQL config file.

[bash]sudo nano /etc/mysql/my.cnf[/bash]

And change:

[bash]bind-address = 127.0.0.1[/bash]

To:

[bash]bind-address = 0.0.0.0[/bash]

Then restart the MySQL server.

[bash]sudo /etc/init.d/mysql restart[/bash]

Setup the databases

Now we need to setup the databases for our video and music library. Login to MySQL, you will be prompted for your password.

[bash]mysql -uroot -p[/bash]

What ever you do don’t create any databases. Kodi will use the name that we supply later but just as a prefix, it will add an additional identifier. We need to create a user for the kodi machines. You can use whatever username and password you like but once again make a note of it.

[bash] CREATE USER ‘kodi’ IDENTIFIED BY ‘password’;[/bash]

The first time Kodi connects it needs to be able to create it’s databases, we need to grant the account full access. We will lock that down later to secure our MySQL if we want to use it for other things then Kodi as well.

[bash]GRANT ALL ON *.* TO ‘kodi’;[/bash]

Test to connect to the database from another machine. Either via MySQL command line tool, the same we used on the Pi, or download MySQL Workbench and test the connection. If all is working just type quit and press enter to return to the main shell.

Setup kodi

Then we need to make a backup of our current media library. I’m using OpenELEC but most Kodi versions should be the same. In the Kodi UI goto System/Settings -> Video -> Library and select Export library. If you don’t see the last options make sure that Settings Oprions is set to Advanced in the lower left corner of the screen. Select Multiple files, this will create .nfo files along side all the media files. I already use this setup since I scan all my libraries With local information only. This is by far the safest way to migrate your library otherwise you have to scrape all the media again. So if you use local information only on your scrapes your good to go. Otherwise do the export!

Now we need to setup Kodi to use the MySQL server. Connect to the Kodi with SSH, username and password depends on the distribution you used to install your Kodi. The location of the userdata folder also varies from different distributions. I have one XBMC installed on a Raspberry Pi where the path is /home/pi/.kodi/userdata. On the OpenELEC installs I’m doing this for the userdata folder is located in /storage/.kodi/userdata/.

When you have found the folder you need to create a file named advancedsettings.xml. You can also do this via smb share if that is enabled on your Kodi machine. I prefer to do it over SSH to prevent any encoding issues. If you want to you can try it out by going \{ip of kodi}userdata.

[bash]sudo advancedsettings.xml[/bash]

In this file we will put the configuration for accessing our MySQL server.

[bash]
<advancedsettings>
<videodatabase>
<type>mysql</type>
<host>{IP address or FQDN of your MySQL server}</host>
<port>3306</port>
<name>{prefix of your db name, I used kodi_video}</name>
<user>kodi</user>
<pass>{password you selected}</pass>
</videodatabase>
<musicdatabase>
<type>mysql</type>
<host>{IP address or FQDN of your MySQL server}</host>
<port>3306</port>
<name>{prefix of your db name, I used kodi_music}</name>
<user>kodi>/user>
<pass>{password you selected}</pass>
</musicdatabase>
<videolibrary>
<importwatchedstate>true</importwatchedstate>
<importresumepoint>true</importresumepoint>
</videolibrary>
</advancedsettings>
[/bash]

Save the file and reboot your Kodi machine. Depending on distribution you may need to do sudo reboot. Once it recycles you can scan your locations again, it will use the local .nfo files you already had or the once created during your export.

Securing MySQL again

We don’t want the kodi MySQL user to have full access going forward. If your not using your MySQL for anything else then you can leave it be, but I want to secure mine. So back to the SSH console on the MySQL server.

[bash]mysql -uroot -p[/bash]

You will once again be prompted for your MySQL root password and then dropped into the MySQL console. So now we check the name of the databases.

[bash]
mysql&amp;amp;amp;gt; show databases;
+——————–+
| Database |
+——————–+
| information_schema |
| kodi_music52 |
| kodi_video93 |
| mysql |
| performance_schema |
+——————–+
5 rows in set (0.00 sec)
[/bash]

So we have the kodi_ databases named after the <name> parameter in our advancedsettings.xml. Now we revoke all the access we gave the kodi account.

[bash]REVOKE ALL PRIVILEGES, GRANT OPTION FROM ‘kodi’;[/bash]

And then only grant it full access to the two kodi_ databases.

[bash]
GRANT ALL ON `kodi%`.* TO ‘kodi’;
flush privileges;
[/bash]

Setting up the other Kodi

Once all this is up and running setting up the second one is really easy. If you enabled SMB shares on OpenELEC like I did you just connect to the first kodi like \kodiUserData copy the advancedsettings.xml and sources.xml to the other kodies UserData share. If you don’t have SMB enabled you have to edit the files via SSH.

 

AWS EC2 Linux: Simple backup script

I have a small EC2 box running a couple of WordPress sites. To back them up I wrote a quick bash script that dumps out the databases and also zips up all the files. This script is running daily, the amount of disc space doesn’t really matter since the sites are really small. If you have larger sites you might want to split the script into one for weekly backup for files and a daily one for databases, the principal are the same.

Prerequisites

  • Credentials for MySQL with access to the database in question. In this example I run with root.
  • A folder where you want to store your backups. You should a separate folder since we clean out old backups in the script.
  • Path to the www root folder for the sites.

Script

First we create a script and open it for editing in nano:

[bash]nano backup.sh[/bash]

First line in any bash script is:

[bash]#! /bin/bash[/bash]

Then we create a variable to keep the current date:

[bash]_now=$(date +"%m_%d_%Y")[/bash]

Then we dump out the databases to .sql files:

[bash]
mysqldump –user root –password=lamedemopassword wp_site_1 > /var/www/html/backup/wp_site_1_$_now.sql
mysqldump –user root –password=lamedemopassword wp_site_2 > /var/www/html/backup/wp_site_2_$_now.sql
[/bash]

Here we use the $_now variable we declared in the beginning of the script. So we can easily find the correct backup if we need to do a restore. Next step is to zip up the www contents of each site:

[bash]
tar -zcvf /var/www/html/backup/www_backup_site1_$_now.tar.gz /var/www/html/wp_site_1
tar -zcvf /var/www/html/backup/www_backup_site2_$_now.tar.gz /var/www/html/wp_site_2
[/bash]

Once again we use the $_now variable to mark the file name with the current date for the backup.

Then we want to clean out backups older then x days. In this example I remove all backup files older then 7 days.

[bash]
find /var/www/html/backup/* -mtime +7 -exec rm {} ;
[/bash]

The first part find /var/www/html/backup/* -mtime +7  then we use the -exec to pipe the result into a command, in this case rm. The {} inserts the files found and the  escapes the command to prevent it to expand the shell. Then we finalize the row with a ;. So this means that for each file found it will execute the rm command and remove that file.

Save the backup.sh file and exit nano. Now we need to make the script executable:

[bash]chmod 755 backup.sh[/bash]

Then we can do a test run of the script:

[bash]sudo ./backup.sh[/bash]

Now check the folder that the files was created and contain the actual data. If your satisfied with the result you can move the script into the cron.daily folder.

[bash]sudo mv /home/ec2-user/backup.sh /etc/cron.daily/[/bash]

Now the daily cronjob will create a backup of the WordPress sites. Both files and databases.

Google Speech API – returns no result

Google Speech API takes a flac audio file and converts it to text. This API is aimed at Chromium and Android developers first and for most so the documentation for other plattforms isn’t that good. This article contains C# code but the principal is the same for other languages. I haven’t looked at this before today but got a question from a developer that was stuck only receiving {“result”:[]} in return from the API even though the HTTP response was 200 OK. I was pretty impressed with the whole thing so here is what I found today and a simple example to get you started.

Get access

To get access to the API you need to obtain an API key. Simply logging into Google Cloud Console, creating a new project and adding the API will not work. First you need to join the Chromium-dev group on Google groups with the same Google account used in the cloud console.

Then you can head over to Google Cloud Console and:

  1. Create a new project.
  2. Go into API and search for Speech API and add it.
  3. Create credentials for the API, select Server Key.
  4. Put in your public IP and click Create.
  5. Save your API key generated somewhere.

Note: The API only allows you 50 requests a day!

Test is out

You can use the same flac file I used or get/create your own. Just make sure its 16-bit PCM and mono. That was the issue causing the {“result”:[]} result.

I used a simple C# windows application with one button running this code:

[csharp]
string api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
string path = @"C:tempgood-morning-google.flac";

byte[] bytes = System.IO.File.ReadAllBytes(path);

WebClient client = new WebClient();
client.Headers.Add("Content-Type", "audio/x-flac; rate=44100");
byte[] result = client.UploadData(string.Format(
"https://www.google.com/speech-api/v2/recognize?client=chromium&lang=en-us&key={0}", api_key), "POST", bytes);

string s = client.Encoding.GetString(result);
[/csharp]

Then you should get this result back:

[js]
{"result":[{"alternative":[{"transcript":"good morning Google how are you feeling today","confidence":0.987629}],"final":true}],"result_index":0}
[/js]

Important about the flac file

For this to work you need to have a 16-bit PCM mono flac file. The issue that the developer I helped with this ran into was that his file was 32-bit float and stereo. If your not sure download Audacity and check/convert your file.

audacity google speech api

AWS EC2 Linux: Enable SSH password logon

Amazon AWS EC2 instances are by default secured with ssh certificates. This is great for security until you need to provide a UAT duplicate for an external user or developer. You don’t want to share your certificate with them and setting up a new one is more work than this quick fix. The security isn’t as important on a UAT or test system as it is on a production system so for this use case we can go for lower security.

To enable users to access we first need to set a password on the ec2-user account. It’s highly recommended that you select a strong password!

[bash]
sudo passwd ec2-user
[/bash]

Then we need to allow password only connections. We edit the ssh settings, find the line PasswordAuthentication no and change it to PasswordAuthentication yes.

[bash]
sudo nano /etc/ssh/sshd_config
[/bash]

Then we need to restart the ssh service.

[bash]
sudo service sshd restart
[/bash]

Now you can login to you Amazon AWS EC2 instance with only a password. To secure the server again just change the PasswordAuthentication line back to no.

Use UNC path in Filezilla server

Filezilla is widely used for ftp servers, it’s open source and easy to setup. It also support SSL encrypted FTP connections which is nice for data security. In one of my setups the need for sharing UNC paths came up. Filezilla actually supports it even though the UI doesn’t. So in a few easy steps we can set it up. Remember that the service account running the Filezilla service needs access to the share.

  1. Setup the account in the UI. Point the directory to “c:temp” or similar.
  2. Open up the “FileZilla Server.xml” located in the Filezilla install directory.
  3. Find the corresponding user node “<User Name=”{username}”>
  4. Under “<Permissions>” you will have an entry for each folder setup. Change the <Permission Dir=”C:temp”> to <Permission Dir=”\servershare”>
  5. Recycle the Filezilla service and you are good to go!

 

You can then change permissions from the UI if you like. So this work around is just needed for creating the link to the share. Once again the FileZilla service account need access to the share. Running it under “System” or “Network Service” will not work in most cases!

AngularJS: $digest loop when filtering array of objects

Ran into trouble trying to use a filter to sort an array of objects in AngularJS. Basic issue is because the array of objects is passed to the filter by reference. This is not unique to AngularJS, javascript always passed arrays by reference. Since AngularJS watches for changes in the array the sort will fire a digest cycle for every change. This will end up with an error similar to this:

Error: $rootScope:infdig
Infinite $digest Loop

[plain]
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);
return b(f,c,e)}","newVal":139,"oldVal":137},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":141,"oldVal":139}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":141,"oldVal":139},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":143,"oldVal":141}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":143,"oldVal":141},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":145,"oldVal":143}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":145,"oldVal":143},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":147,"oldVal":145}],
[{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":147,"oldVal":145},
{"msg":"fn: function (c,e,f,g){f=d&amp;amp;amp;&amp;amp;amp;g?g[0]:a(c,e,f,g);return b(f,c,e)}","newVal":149,"oldVal":147}]]
[/plain]

Background

So the data structure looks something like this:

[js]
{
"feed": {
"entry": [
{
"gphoto$id": {
"$t": "1000000482404626"
},
"updated": {
"$t": "2016-01-31T19:15:32.739Z"
},
"title": {
"$t": "Auto Backup",
"type": "text"
},
"gphoto$numphotos": {
"$t": 4223
},
"published": {
"$t": "2016-01-31T17:44:14.000Z"
}
},
{
"gphoto$id": {
"$t": "6056987966618281201"
},
"updated": {
"$t": "2015-09-04T15:47:26.370Z"
},
"title": {
"$t": "Norge 2014-09",
"type": "text"
},
"gphoto$numphotos": {
"$t": 368
},
"published": {
"$t": "2014-09-09T08:46:46.000Z"
}
},
{
"gphoto$id": {
"$t": "5980880175059657089"
},
"updated": {
"$t": "2016-01-23T02:50:47.003Z"
},
"title": {
"$t": "Testalbum2",
"type": "text"
},
"gphoto$numphotos": {
"$t": 3
},
"published": {
"$t": "2014-02-16T06:29:40.000Z"
}
}],
"xmlns": "http://www.w3.org/2005/Atom&quot;,
"xmlns$gphoto": "http://schemas.google.com/photos/2007&quot;
},
"version": "1.0",
"encoding": "UTF-8"
}
[/js]

We store this array in a $scope variable called List. When we want to run through this array in a simple ng-repeat like

[html]ng-repeat="album in List | BasicFilter:Field:Reverse track by album.gphoto$id.$t"[/html]

It will call the filter every time the List variable is changed. If we use this filter:

[js]
.filter(‘BasicFilter’, function() {
return function(items, field, reverse) {
items.sort(function(a, b){
var aValue = a[field].$t.toLowerCase();
var bValue = b[field].$t.toLowerCase();

if(reverse)
{
return ((aValue &amp;gt; bValue) ? -1 : ((aValue &amp;lt; bValue) ? 1 : 0));
} else {
return ((aValue &amp;lt; bValue) ? -1 : ((aValue &amp;gt; bValue) ? 1 : 0));
}
});

return items;
};
})
[/js]

Why?

This filter takes in the array as items and sort it. Without going in to to much detail the function supplied to .sort will fire a lot of times. Since the array items is passed by reference it will end up updating the $scope.List that Angular watches and fire a digest each time. There for the fail safe kicks in and you get the error from before.

Solution

So how do we get around this? We need to sort the array without updating the original to prevent the digest from running each time. So we modify our filter to:

[js]
.filter(‘SlicedFilter’, function() {
return function(input, field, reverse) {
var items = input.slice();
items.sort(function(a, b){
var aValue = a[field].$t.toLowerCase();
var bValue = b[field].$t.toLowerCase();

if(reverse)
{
return ((aValue > bValue) ? -1 : ((aValue < bValue) ? 1 : 0));
} else {
return ((aValue < bValue) ? -1 : ((aValue > bValue) ? 1 : 0));
}
});

return items;
};
})
[/js]

By renaming the items variable to input and then create items as an internal variable and copy the original array with .slice() we will get around this issue. Slice will create a shallow copy of the object array. That means that all simple types like strings and numbers will be copied while objects inside the array will be by reference. This means that if any of the objects is updated in the original array the changes will reflect in our copy as well. Then we sort our new copy and when we are done, running the function passed to sort many times, we return the array. When the filter finally return the sorted copy that will trigger the digest and DOM update. So instead of triggering it to many times we only trigger it when we are done with our sorting returning the final product.

But what if the user decides to change back to a sort order we already used? We will sort once again and return it, that’s not really efficient! So let’s cache the sorted list:

[js]
.filter(‘CachedFilter’, function() {
return _.memoize(function(input, field, reverse) {
var items = input.slice();
items.sort(function(a, b){
var aValue = a[field].$t.toLowerCase();
var bValue = b[field].$t.toLowerCase();

if(reverse)
{
return ((aValue > bValue) ? -1 : ((aValue < bValue) ? 1 : 0));
} else {
return ((aValue < bValue) ? -1 : ((aValue > bValue) ? 1 : 0));
}
});

return items;
}, function(items, field, reverse) {
return items.length + field + reverse;
});
})
[/js]

This is a function from underscoreJS that will cache output from whatever function you give it. The second function we pass in calculates the key for the cache. In our case that would be the number of items in the array, field we filtered on and if it’s ascending or descending. So the next time we request the same sort we will get the cached version which increases performance.

I put together a demo on Plunker for this issue

Please share, comment and add suggestions below!

Implementing Google OAuth in JSFiddle

The implementation of Google OAuth for use with there API’s are simple with there API Client Library for JavaScript, if you are developing a normal webpage. If you want the functionality in a JSFiddle it’s a little more complex for several reasons. I have put together a demo on JSFiddle that shows how it can be accomplished!

Background

I wanted to be able to include Google Oauth for API access in demos I create on JSFiddle. I did a few tests with the API Client Library for JavaScript provided by Google. Ran into several issues with cross-site limitations and sandboxing. Since JSFiddle runs in i sandboxed iframes it limits access to navigation, URL reading and other stuff that the client.js relies on to authenticate the user and get the token back. I found that if the user is already authenticated with Google and you set the immediate parameter to true it will work. That’s probably good enough for several demos but it’s not a clean solution so I spent a few hours and came up with a solution. If the user isn’t already authenticated two new tabs will open up and nothing more will happen.

Pre-requirements

You need to setup a project in the Google Cloud Platform Console and get the clientID. You also need to setup Authorized redirect URIs that matches your JSFiddle. Due to the iframe nature of JSFiddle you cant use the jsfiddle.net address, since the result frame being served from the fiddle.jshell.net domain. For this demo I setup these two redirect URIs:

One address for the case of direct access to the fiddle (someone fining it via Google for example) and one if they navigate to it from my dashboard.

To get this to work I implemented the OAuth flow from the ground up. The demo uses jquery, because it’s easier, but it can be done in pure JavaScript if you want to. I’m using the standard Oauth 2 flow were you send the user to Google for login with your client_id, redirect_uri and scope as query string parameters. The user authenticates with Google and get’s redirected back to the provided redirect_uri with access_token, token_type and expires_in parameters as query string parameters. Google OAuth supports redirecting the user back to a http address but that’s no good from a security standpoint! JSFiddle does support https on all it’s fiddles so let’s make use of it!

Different then a standard JSFiddle

There are a few things that are different from a regular JSFiddle implementation, to create a safe demo that handles the iframe limitations.

  • Check for HTTPS
    This is just for making the demo safe. When Google returns the user to the site with the token as query string parameter it is possible to intercept if you don’t use HTTPS. A simple implementation of location.protocol == ‘https:’ checks if we are secure or not. This isn’t needed since you can send the user from a HTTP site to Google and get the user back on a HTTPS redirect. This is just put there to make a point of the HTTPS implementation.
  • The use of fiddle.jshell.net instead of jsfiddle.net
    Since the iframe with the result, where all the javascript is executed, is served from fiddle.jshell.net all the content needs to be served from there. If not the script cant access the parent frame URL containing the access token returned from Google.

Points of interest in the code

  • Check for HTTPS
    As above this isn’t really needed since I can set the redirect_uri to whatever address I want as long as it’s specified in the cloud console.
  • Create the authentication link
    For this to work properly it needs to be a target=”_blank” link. Navigation inside the iframe is blocked outside the jsfiddle domains. This is the reason why the demo needs to open a new tab to be able to return with the access token. The parameters at the top will set the basic information for building the link:

    [javascript]
    var oAuthEndPoint = "https://accounts.google.com/o/oauth2/auth&quot;;
    var oAuthClientID = "702841265150-iaravt3jmu0c67k892pivj9kgkb8dbco.apps.googleusercontent.com";
    var oAuthScope = "https://www.googleapis.com/auth/userinfo.profile&quot;;
    [/javascript]

    Then we build the redirect uri from the current JSFiddle address. I implemented it this way to make it easy to fork it to create other demos based on this implementation. I use the a element for easy manipulation of the domain and path. Replacing the protocol and domain with https://fiddle.jshell.net:

    [javascript]
    var a = document.createElement(‘a’);
    a.href = document.referrer;
    var redirect_uri = [‘https://fiddle.jshell.net&#8217;, a.pathname].join(”);
    a = ”;
    [/javascript]

    document.referrer is used to get the actual address of the fiddle from the parent frame, even if the domains doesn’t match at this point. An easy way to get around the same origin limitations of the iframe.

  • User clicks the authentication link/button
    The user is sent to Google to authenticate and allow the app access to the user information. The only scope requested in this demo is basic profile information.
  • User is returned to JSFiddle
    When the user is returned the fiddle will load all over again. So each time it loads it will check if HTTPS is used, if so check for the URL parameter access_token. This is why we need to use the fiddle.jshell.net domain, it will make all the iframes of the fiddle to load from the same domain giving our script access to it’s parent. We need that access to grab the URL and extract the parameters. This function grabs different parameters from the URL of the parent frame:

    [javascript]
    function GetURLParameter(sParam) {
    var sPageURL = window.parent.location.hash.substring(1);
    var sURLVariables = sPageURL.split(‘&amp;amp;amp;amp;amp;’);

    for (var i = 0; i &amp;amp;amp;amp;lt; sURLVariables.length; i++) {
    var sParameterName = sURLVariables[i].split(‘=’);
    if (sParameterName[0] == sParam) {
    return sParameterName[1];
    }
    }
    }
    [/javascript]

  • Use the token for API access
    As soon as we have the token we can use that for accessing the API with a simple ajax request via this simple function:

    [javascript]
    function LoadUserInfo(access_token, expires_in) {
    $.ajax({
    url: ‘https://www.googleapis.com/userinfo/v2/me&#8217;,
    type: ‘GET’,
    dataType: ‘json’,
    headers: {
    ‘Authorization’: ‘Bearer ‘ + access_token
    },
    success: function (data) {
    // Hide login
    $("#login").hide();

    // Populate demo, img and name
    $("#user_pic").attr("src", data.picture);
    $("#user_name").attr("href", data.link);
    $("#user_name").text(data.name);

    // Show raw data
    for (var property in data) {
    if (data.hasOwnProperty(property)) {
    $("#raw_data").append("<b>" + property + "</b>" + data[property] + "<br/>");
    }
    }

    // Display demo
    $("#demo").show();
    },
    error: function () {
    $(‘#demo’).html(‘<p>An error has occurred</p>’);
    }
    });
    }
    [/javascript]

Limitations

So there are a few limitations with this approach. You get the token that is valid for 3600 seconds (one hour) and implementing code for renewing that with out losing state is not possible. So if the user have started creating something and the token expires renewing it will end up in a reload of the fiddle. The other, smaller limitation, is that there always will be a second tab opened to authenticate the user.

You also need to set the code as base in the fiddle at all times, redirects to a version number like /3 will make the redirect back from Google to fail!

Any thoughts, questions or suggestions? Please share in the comments below!

Complete JSFiddle demo

Embed image in HTML code

You can embed images straight into HTML code without loading a separate image file. This is called data URL and is a base 64 encoded representation of the actual image file. You can more or less embed what ever data you like this way but the only implementations I have seen so far is with images. So why do this? There are probably hundreds of examples where this can be useful, I have used it for:

  • For taglines on services like JSFiddle, where external hosting of files can be a mess. If I delete the file or my server is down my JSFiddle demos will be broken.
  • E-mail signatures, same thing here no dependence on external servers for serving images in my mail signature. Some e-mail clients out there doesn’t display data URL’s so be careful with this one.
  • Screenshots in documentation distributed with software. Since the file is loaded from the users local hard drive the bigger size (see below) isn’t an issue for any image and I only have to distribute on HTML file with the complete documentation.

So why isn’t every image put into HTML this way? The answer is simple, the size increases! For small images, usually part of the layout, can be loaded faster even though the size is increased. This is due to the lack of overhead from an additional http request to get the image file. Every time someone opens an HTML page over the internet the browser then open new http connections for each resource referenced in the HTML like images, css and javascript files. So by cutting down on the number of request the page can load faster.

Dataurl.net by Sveinbjorn Thordarson is a really good resource to check what files on your site that would be suitable for data url embeds.

Simple example of a base 64 encoded data url image, my tagline image:

[html]
<img class="brand-img" alt="Kristofer Källsbo aka Hackviking"    src="data:image/jpeg;base64,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">
[/html]

This code will result in this:
Kristofer Källsbo aka Hackviking

I put together a JSFiddle with some example code for converting images to base 64 encoded data url’s using HTML5 components and plain javascript:

Win32 Disk Imager

Reading and writing images to SD cards made easy! I more or less us it every day to write images to SD cards for my Raspberry Pi projects or for doing a backup of them. Win32 Disk Imager has received some bad press because it some times breaks SD cards. Every time that happen to me it was the image that was bad, so I can not really agree with the bad comments. If the card becomes unreadable it’s easily fixed with SD Formatter. Win32 Disk Imager can be downloaded from SourceForge!