<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>javascript &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/javascript/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Fri, 17 Apr 2026 06:45:45 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>javascript &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Reasonably accurate, privacy conscious, cookieless, visitor tracking for WordPress]]></title>
		<link>https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/</link>
					<comments>https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 11 Sep 2025 11:34:39 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[seo]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=63158</guid>

					<description><![CDATA[I am vain. I like to know which of my blog posts have &#34;done numbers&#34;. I get a little thrill knowing that an old post I wrote has been read by someone in a land I&#039;ve never visited. I&#039;m curious and want to know if a newsletter has linked to me.  At the same time, I don&#039;t want to know too much about people. I don&#039;t want to stalk them around the web. I refuse to care how long they spend with me. I…]]></description>
										<content:encoded><![CDATA[<p>I am vain. I like to know which of my blog posts have "done numbers". I get a little thrill knowing that an old post I wrote has been read by someone in a land I've never visited. I'm curious and want to know if a newsletter has linked to me.</p>

<p>At the same time, I don't want to know <em>too</em> much about people. I don't want to stalk them around the web. I refuse to care how long they spend with me. I can't be bothered setting up a foolproof system that captures 100% accurate information.</p>

<p>After trying several analytics plugins for WordPress, I've decided to have a go at writing my own<sup id="fnref:learn"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#fn:learn" class="footnote-ref" title="I enjoy learning. If you're about to say &quot;Why not just install…&quot; then you've missed the point. I like understanding how things work, I get joy from discovering some new function, my brain feels happy…" role="doc-noteref">0</a></sup>.</p>

<p>Before embarking on this, please do read "<a href="https://blog.yossarian.net/2023/12/24/You-dont-need-analytics-on-your-blog">You Don't Need Analytics on Your Blog</a>" and the slightly more provocative "<a href="https://www.thisdaysportion.com/posts/contra-analytics/">You do not need “analytics” for your blog because you are neither a military surveillance unit nor a commodity trading company</a>". Both give excellent examples of why this is at best foolish and at worse injurious.  Proceed with caution in your heart.</p>

<h2 id="background"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#background">Background</a></h2>

<p>As a consequence of the way the web works, every time you click on a link the website's owner gets the following pieces of information.</p>

<ul>
<li>The time you clicked,</li>
<li>The page you visited,</li>
<li>The name of the web browser you use,</li>
<li>The URl of the page which you clicked to get here,</li>
<li>The IP address your computer has.</li>
</ul>

<p>There are a few other things sent along but they're not interesting to me.</p>

<p>Using that information, I can construct a reasonably accurate view of how many times a post has been viewed and how many people viewed it.</p>

<h2 id="defining-a-page-view"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#defining-a-page-view">Defining a page view</a></h2>

<p>If a web page is loaded, that counts as a view. I'm not going to track whether the user stayed for more than 30 seconds or closed their browser in disgust after reading the headline. If the page is loaded, that's a view.</p>

<p>But what if one person repeatedly hits refresh on the same post?  To deal with that, I'll need a concept of a visitor.</p>

<h2 id="defining-a-visitor"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#defining-a-visitor">Defining a visitor</a></h2>

<p>The "normal" way of doing things is to stick a cookie in the user's browser and track them that way. I can't be bothered with that. And, besides, it doesn't account for a person reading on their laptop and then moving to their phone.</p>

<p>So I'm going to use a proxy by creating a cryptographic hash of the visitor's IP address and the browser's User Agent string.</p>

<p>Of course, a household might have one IP address and multiple people with the same phone. But, equally, one person might rove over several WiFi networks in the course of one browsing session, getting a different IP each time.</p>

<p>The aim is to be <em>reasonably</em> accurate.</p>

<p>Hashing the contents means I don't need to store the user's IP address. Once hashed, the information becomes a string like <code>db050e7b853e5856</code> which is functionally impossible to <a href="https://www.techsolvency.com/passwords/dehashing-reversing-decrypting/">crack</a> back to an IP address &amp; UA string<sup id="fnref:orisit"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#fn:orisit" class="footnote-ref" title="Or is it? There are 4 billion IPv4 addresses - although slightly fewer in actual use. Creating a rainbow table with 4 billion rows is possible if I was just using IP addresses. But there are an…" role="doc-noteref">1</a></sup>.</p>

<p>This also means that I can redefine the concept of a page view. If the same visitor refreshed the page multiple times, it will only count as a single visit.</p>

<p>I'll reset the counter at midnight in my local timezone. If someone visits just before midnight and then just after, it'll count as two visits. Oh well.</p>

<h2 id="where-did-they-come-from"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#where-did-they-come-from">Where did they come from?</a></h2>

<p>Generally speaking, there are two ways that visitors share their referrer. One is the "referer" header (yes, it is misspelled). It contains a URl of the referring site or application. For example, if someone clicked from a search result it might say <code>https://yahoo.com</code>.</p>

<p>The other way is using "Urchin Tracking Module" query strings. At the end of the URl they visit, they might append something like <code>?utm_source=alices-newsletter</code>.</p>

<p>Some sites, like Reddit, might use multiple subdomains - <code>old.reddit.com</code> or <code>out.reddit.com</code> - so some deduplication may be necessary.</p>

<h2 id="where-in-the-world-are-they"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#where-in-the-world-are-they">Where in the world are they?</a></h2>

<p>A user's IP address is <em>somewhat</em> accurate method of detecting their location. Yes, users could be proxying through a VPN or using a SIM card from a foreign country. But this isn't an exercise in precise tracking. Rough and ready is fine.</p>

<p>There are a variety of <a href="https://mailfud.org/geoip-legacy/">GeoIP Databases</a> which are updated semi-regularly. I'm only interested in the country of origin, I don't care about finer resolution than that.</p>

<p>Again, the aim isn't precise targetting. I'd just like to know that people in Sudan ever read my blog posts.</p>

<h2 id="what-else-could-we-use"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#what-else-could-we-use">What else could we use?</a></h2>

<p>It <em>might</em> be nice to know if someone is using a small-screen or large device. But my CSS is responsive, so I don't care.</p>

<p>Similarly, their Internet connection speed might be available. But, again, I try to optimise things so that isn't necessary to know.</p>

<p>Do I need to know if someone speaks Hungarian? No. There's nothing useful I can do with that information.</p>

<p>Could I extract their operating system, device, and browser from their User-Agent? I guess. Would I use the information that X% of my readers use Firefox on Linux? Doubtful!</p>

<h2 id="collect-the-information"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#collect-the-information">Collect the information</a></h2>

<p>There are two main methods of collecting these data.</p>

<p>First is a "no JavaScript" solution. This tells the browser to request an image which has a query string to send along the details of the page requested.</p>

<pre><code class="language-php">&lt;noscript&gt;
    &lt;img src="/tracking.php?ID=&lt;?php echo $postID ?&gt;" alt="" width=1 height=1 class=hidden&gt;
&lt;/noscript&gt;
</code></pre>

<p>The downside is that there's no way to capture referer information. If each page were dynamically generated, I could grab it from PHP's <code>$_SERVER</code> superglobal. But my website is heavily cached, so that isn't possible.</p>

<p>It <em>is</em> possible to use JavaScript to dynamically send the information for collection:</p>

<pre><code class="language-js">let formData = new FormData();
formData.append("HTTP_REFERER", document.referrer);
formData.append("ID",  &lt;?php echo $postID ?&gt;);

fetch("/tracking.php", {
    method: "POST",
    body: formData,
});
</code></pre>

<p>This approach has three distinct advantages.</p>

<ol>
<li>It works whether the user has JS enabled or not.</li>
<li>Repeated requests for the same page will usually reload the image from cache, so won't double-count.</li>
<li>It doesn't count hits from bots. They typically don't execute JavaScript or don't request images.</li>
</ol>

<h2 id="bot-detection"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#bot-detection">Bot Detection</a></h2>

<p>Not all traffic originates from humans. There are lots of bots which crawl the web. Some are useful - like search engines building up a map. Others are harmful - like AI agents aggressively scraping content to plagiarise.</p>

<p>There are <a href="https://www.humansecurity.com/learn/blog/crawlers-list-known-bots-guide/">lots of identifiable bots</a> out there - and more which obfuscate themselves. There are some, like <a href="https://github.com/GoogleChrome/lighthouse/pull/14384">Lighthouse</a> which cloak themselves.</p>

<p>I'm not trying to eliminate everything which <em>could</em> be a bot. I am trying for <em>reasonably</em> accurate. So I eliminate any User-Agent which contains:</p>

<p><code>"/bot|crawl|spider|seo|lighthouse|facebookexternalhit|preview|HeadlessChrome/i"</code></p>

<p>There are some <a href="https://github.com/fabiomb/is_bot">big lists of bots</a> you can use - but they don't seem to trigger my analytics because they aren't requesting the images or executing the JS.</p>

<h2 id="what-bits-of-the-site-to-measure"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#what-bits-of-the-site-to-measure">What bits of the site to measure?</a></h2>

<p>I only care about how many visitors my posts and pages get. I don't need to know if someone visited a tag page, or scrolled back to page 100 of posts from 2019. Those sorts of deep pages are usually only accessed by bots anyway.</p>

<p>I also don't want to count visits from me, myself, and I.</p>

<p>So the tracking is only inserted on single pages which are viewed by non-admins:</p>

<pre><code class="language-php">if ( is_singular() &amp;&amp; !current_user_can( "edit_posts" ) ) {
    …
}
</code></pre>

<h2 id="oddities"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#oddities">Oddities</a></h2>

<p>Sometimes, the URl requested will look something like: <code>https://shkspr-mobi.translate.goog</code> - that just means Google has translated it.</p>

<p>Sometimes, the referer will look something like: <code>android-app://com.google.android.gm/</code> - that just means they clicked from an Android app.</p>

<p>Sometimes, the URl requested will include a fragment or a query string - they can be ignored.</p>

<p>Sometimes, the <code>utm_</code> will contain all sorts of weird stuff. It isn't always possible to pull out exactly where it has come from.</p>

<p>Sometimes, the referer and <code>utm_</code> will disagree. Ah well, never mind.</p>

<p>Sometimes, RSS views are counted and sometimes not. Perhaps I should fix that?</p>

<p>Sometimes, users block trackers or use a text-only browser. That's fine, they can keep their secrets.</p>

<h2 id="saving-the-data"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#saving-the-data">Saving the data</a></h2>

<p>I started this by just shoving what I collected into a CSV.</p>

<pre><code class="language-php">//  Write the CSV.
$line = [date("c"), $ID, $UA, $referer, $domain, $country, $user];
//  Date-based filename.
$filename = "log-" . date("Y-m-d") . ".csv";
//  Append mode.
$handle = fopen( $filename, "a" );
fputcsv( $handle, $line );
fclose( $handle );
</code></pre>

<p>Nothing fancy. Something easily grepable with the ability to query it in more detail if I need.  At the number of hits that my site gets, it is less than 1MB per day.</p>

<p>I've since moved it into a single MySQL table. That might not be sustainable with hundreds of thousands of rows. But that's tomorrow's problem.</p>

<h2 id="accuracy"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#accuracy">Accuracy</a></h2>

<p>I've been running this for a couple of days - simultaneously with my other, more professional, stats plugin. It is within 5% accuracy. It appears to <em>slightly</em> exaggerate the number of visitors and undercount my page-views. That's good enough for my purposes and probably good for my ego!</p>

<h2 id="putting-it-all-together"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#putting-it-all-together">Putting it all together</a></h2>

<p>You can take a look at all the code <a href="https://gitlab.com/edent/blog-theme/">on my GitLab repo</a>.</p>

<h2 id="what-does-it-look-like"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#what-does-it-look-like">What does it look like?</a></h2>

<p>If you've made it this far, you can have a little pictorial treat! Aren't you lucky?</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/09/stats-view.webp" alt="Three tables. One showing referers with colourful favicons. Another countries with colourful emoji flags. One a list of pages and views." width="2450" height="1400" class="aligncenter size-full wp-image-63260">

<h2 id="whats-next"><a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#whats-next">What's next?</a></h2>

<p>For now, a simple table structure is fine. I've shoved it in a basic database. Sure, I don't have any indexes or fancy stuff like that. But modern computers are pretty fast.</p>

<p>Eventually I'll need to create some new tables which will consolidate the data. Perhaps a table for individual posts, using date and country? Or maybe referer? I'll have to see.</p>

<p>I also need a way to get historic data into it. I've blog stats going back to 2009 which I am anxious not to lose.</p>

<p>And, yeah, I'll need a better front-end than manually running SQL queries.</p>

<p>Above all, I want to keep it simple enough that my puny mortal brain can understand it after several years of not touching anything. I want to build something which can run without constant maintenance.</p>

<p>Remember, this is only an exercise in self-learning, self-hosting, and self-respect.</p>

<div id="footnotes" role="doc-endnotes">
<hr>
<ol start="0">

<li id="fn:learn">
<p>I enjoy learning. If you're about to say "Why not just install…" then you've missed the point. I like understanding how things work, I get joy from discovering some new function, my brain feels happy when it is working on a problem. I don't want to just click install, hit next a few times, and fiddle with a few options. <a href="https://shkspr.mobi/blog/2020/12/build-dont-buy/">I've written more about my philosophy here</a>.&nbsp;<a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#fnref:learn" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:orisit">
<p>Or is it? There are 4 billion IPv4 addresses - although slightly fewer in actual use. Creating a rainbow table with 4 billion rows is possible if I was <em>just</em> using IP addresses. But there are an almost infinite variety of User Agent strings. It is probably possible to create a rainbow table of, for example, the 10 most popular UAs, concatenate them with every possible IP address, and then see which hashes to <code>65fef01fef257963</code>. But even then, what would that get an attacker? Knowing that the most popular model of iPhone is on a mobile network's IP range isn't exactly private information.&nbsp;<a href="https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/#fnref:orisit" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=63158&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/09/reasonably-accurate-privacy-conscious-cookieless-visitor-tracking-for-wordpress/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[1KB JS Numbers Station]]></title>
		<link>https://shkspr.mobi/blog/2025/07/1kb-js-numbers-station/</link>
					<comments>https://shkspr.mobi/blog/2025/07/1kb-js-numbers-station/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 20 Jul 2025 11:34:53 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[tts]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=62005</guid>

					<description><![CDATA[Code Golf is the art/science of creating wonderful little demos in an artificially constrained environment. This year the js1024 competition was looking for entries with the theme of &#34;Creepy&#34;.  I am not a serious bit-twiddler. I can&#039;t create JS shaders which produce intricate 3D worlds in a scrap of code. But I can use slightly obscure JavaScript APIs!  There&#039;s something deliciously creepy about…]]></description>
										<content:encoded><![CDATA[<p>Code Golf is the art/science of creating wonderful little demos in an artificially constrained environment. This year the <a href="https://js1024.fun/">js1024 competition</a> was looking for entries with the theme of "Creepy".</p>

<p>I am not a serious bit-twiddler. I can't create JS shaders which produce intricate 3D worlds in a scrap of code. But I <em>can</em> use slightly obscure JavaScript APIs!</p>

<p>There's something deliciously creepy about <a href="https://priyom.org/number-stations">Numbers Stations</a> - the weird radio frequencies which broadcast seemingly random numbers and words. Are they spies communicating? Commands for nuclear missiles? Long range radio propagation tests? Who knows!</p>

<p>So I decided to build one. <a href="https://js1024.fun/demos/2025/24/bar">Play with the demo</a>.</p>

<p>Obviously, even the <a href="https://shkspr.mobi/blog/2020/09/a-floppy-disk-mp3-player-using-a-raspberry-pi/">most extreme opus compression</a> can't fit much audio into 1KB. Luckily, JavaScript has you covered! Most modern browsers have a built-in Text-To-Speech (TTS) API.</p>

<p>Here's the most basic example:</p>

<pre><code class="language-js">m = new SpeechSynthesisUtterance;
m.text = "Hello";
speechSynthesis.speak(m);
</code></pre>

<p>Run that JS and your computer will speak to you!</p>

<p>In order to make it creepy, I played about with the rate (how fast or slow it speaks) and the pitch (how high or low).</p>

<pre><code class="language-js">m.rate=Math.random();
m.pitch=Math.random()*2;
</code></pre>

<p>It worked disturbingly well! High pitched drawls, rumbling gabbling, the languid cadence of a chattering friend. All rather creepy.</p>

<p>But <em>what</em> could I make it say? Getting it to read out numbers is pretty easy - this will generate a random integer:</p>

<pre><code class="language-js">s = Math.ceil( Math.random()*1000 );
</code></pre>

<p>But a list of words would be tricky. There's not much space in 1,024 bytes for anything complex. The rules say I can't use any external resources; so are there any <em>internal</em> sources of words? Yes!</p>

<pre><code class="language-js">Object.getOwnPropertyNames( globalThis );
</code></pre>

<p>That gets all the properties of the global object which are available to the browser! Depending on your browser, that's over 1,000 words!</p>

<p>But there's a slight problem. Many of them are quite "computery" words like "ReferenceError", "URIError", "Float16Array". I wanted all the <em>single</em> words - that is, anything which only has one capital letter and that's at the start.</p>

<pre><code class="language-js">const l = (n) =&gt; {
    return ((n.match(/[A-Z]/g) || []).length === 1 &amp;&amp; (n.charAt(0).match(/[A-Z]/g) || []).length === 1);
};

//   Get a random result from the filter
s = Object.getOwnPropertyNames( globalThis ).filter( l ).sort( ()=&gt;.5-Math.random() )[0]
</code></pre>

<p>Rather pleasingly, that brings back creepy words like "Event", "Atomics", and "Geolocation".</p>

<p>Of course, Numbers Stations don't just broadcast in English.  The TTS system can vocalise in multiple languages.</p>

<pre><code class="language-js">//   Set the language to Russian
m.lang = "ru-RU";
</code></pre>

<p>OK, but where do we get all those language strings from? Again, they're built in and can be retrieved randomly.</p>

<pre><code class="language-js">var e = window.speechSynthesis.getVoices();
m.lang = e[ (Math.random()*e.length) |0 ]
</code></pre>

<p>If you pass the TTS the number 555 and ask it to speak German, it will read out <i lang="de">fünfhundertfünfundfünfzig</i>.</p>

<p>And, if you tell the TTS to speak an English word like "Worker" in a foreign language, it will pronounce it with an accent.</p>

<p>Randomly altering the pitch, speed, and voice to read out numbers and dissociated words produces, I think, a rather creepy effect.</p>

<script>const l = (n) => {
    return ((n.match(/[A-Z]/g) || []).length === 1 && (n.charAt(0).match(/[A-Z]/g) || []).length === 1);
};
m = new SpeechSynthesisUtterance;

function g() {
    setInterval(() => {
        s = Object.getOwnPropertyNames(globalThis).filter(l).sort(() => .5 - Math.random())[0]
        if (Math.random() > .3) {
            s = Math.ceil(Math.random() * 1000);
        }
        var e = window.speechSynthesis.getVoices();
        m.rate = Math.random(), m.pitch = Math.random() * 2, m.text = s, m.lang = e[(Math.random() * e.length) | 0]["lang"];
        speechSynthesis.speak(m);
    }, 2501);
}</script>

<p>If you want to test it out, you can press this button. I find that it works best in browsers with a good TTS engine - let me know how it sounds on your machine.</p>

<p><button onclick="g()">🅝🅤🅜🅑🅔🅡🅢 🅢🅣🅐🅣🅘🅞🅝</button></p>

<p>With the remaining few bytes at my disposal, I produced a quick-and-dirty random pattern using Unicode drawing blocks. It isn't very sophisticated, but it does have a little random animation to it.</p>

<p>You can <a href="https://js1024.fun/demos/2025">play with all the js1024 entries</a> - I would be delighted if you voted <a href="https://js1024.fun/demos/2025/24/bar">for mine</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=62005&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/07/1kb-js-numbers-station/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Using the Web Crypto API to Generate TOTP Codes in JavaScript Without 3rd Party Libraries]]></title>
		<link>https://shkspr.mobi/blog/2025/03/using-the-web-crypto-api-to-generate-totp-codes-in-javascript-without-3rd-party-libraries/</link>
					<comments>https://shkspr.mobi/blog/2025/03/using-the-web-crypto-api-to-generate-totp-codes-in-javascript-without-3rd-party-libraries/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 01 Mar 2025 12:34:57 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[CyberSecurity]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[totp]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=58536</guid>

					<description><![CDATA[The Web Crypto API is, thankfully, nothing to do with scammy cryptocurrencies. Instead, it provides access to powerful cryptographic features which were previously only available in 3rd party tools.  So, is it possible to build a TOTP code generator without using any external JS libraries? Yes! And it is (relatively) simple.  Here&#039;s the code that I&#039;ve written. It is slightly verbose and contains…]]></description>
										<content:encoded><![CDATA[<p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API">Web Crypto API</a> is, thankfully, nothing to do with scammy cryptocurrencies. Instead, it provides access to powerful cryptographic features which were previously only available in 3rd party tools.</p>

<p>So, is it possible to build a TOTP<sup id="fnref:sigh"><a href="https://shkspr.mobi/blog/2025/03/using-the-web-crypto-api-to-generate-totp-codes-in-javascript-without-3rd-party-libraries/#fn:sigh" class="footnote-ref" title="*sigh* Please don't be the boring dolt who makes a joke about Top of The Pops. Yes, I know they share the same initialism. And, yes, it's funny how nonce means something different in cryptography…" role="doc-noteref">0</a></sup> code generator without using <em>any</em> external JS libraries? Yes! And it is (relatively) simple.</p>

<p>Here's the code that I've written. It is slightly verbose and contains a lot of logging so you can see what it is doing. I've annotated it with links to the various specifications so you can see where some of the decisions come from. I've compared the output to several popular TOTP code generators and it <em>appears</em> to match. You probably shouldn't use this in production, and you should audit it thoroughly.</p>

<p>I'm sure there are bugs to be fixed and performance enhancements to be made. Feel free to leave a comment here <a href="https://codeberg.org/edent/TOTP_Test_Suite">or on the repo</a> if you spot anything.</p>

<p>I consider this code to be trivial but, if it makes you happy, you may consider it licensed under MIT.</p>

<pre><code class="language-js">async function generateTOTP( 
    base32Secret = "QWERTY", 
    interval = 30, 
    length = 6, 
    algorithm = "SHA-1" ) {

    //  Are the interval and length valid?
    if ( interval &lt;  1 ) throw new Error( "Interval is too short" );
    if ( length   &lt;  1 ) throw new Error( "Length is too low"     );
    if ( length   &gt; 10 ) throw new Error( "Length is too high"    );

    //  Is the algorithm valid?
    //  https://datatracker.ietf.org/doc/html/rfc6238#section-1.2
    algorithm = algorithm.toUpperCase();
    if ( algorithm.match( "SHA-1|SHA-256|SHA-384|SHA-512" ) == null ) throw new Error( "Algorithm not known" );

    //  Decode the secret
    //  The Base32 Alphabet is specified at https://datatracker.ietf.org/doc/html/rfc4648#section-6
    const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let bits = "";

    //  Some secrets are padded with the `=` character. Remove padding.
    //  https://datatracker.ietf.org/doc/html/rfc3548#section-2.2
    base32Secret = base32Secret.replace( /=+$/, "" )

    //  Loop through the trimmed secret
    for ( let char of base32Secret ) {
        //  Ensure the secret's characters are upper case
        const value = alphabet.indexOf( char.toUpperCase() );

        //  If the character doesn't appear in the alphabet.
        if (value === -1) throw new Error( "Invalid Base32 character" );

        //  Binary representation of where the character is in the alphabet
        bits += value.toString( 2 ).padStart( 5, "0" );
    }

    //  Turn the bits into bytes
    let bytes = [];
    //  Loop through the bits, eight at a time
    for ( let i = 0; i &lt; bits.length; i += 8 ) {
        if ( bits.length - i &gt;= 8 ) {
                bytes.push( parseInt( bits.substring( i, i + 8 ), 2 ) );
        }
    }

    //  Turn those bytes into an array
    const decodedSecret = new Uint8Array( bytes );
    console.log( "decodedSecret is " + decodedSecret )

    //  Number of seconds since Unix Epoch
    const timeStamp = Date.now() / 1000; 
    console.log( "timeStamp is " + timeStamp )

    //  Number of intervals since Unix Epoch
    //  https://datatracker.ietf.org/doc/html/rfc6238#section-4.2
    const timeCounter = Math.floor( timeStamp / interval );
    console.log( "timeCounter is " + timeCounter )

    //  Number of intervals in hexadecimal
    const timeHex = timeCounter.toString( 16 );
    console.log( "timeHex is " + timeHex )

    //  Left-Pad with 0
    paddedHex = timeHex.toString(2).padStart( 16, "0" );
    console.log( "paddedHex is " + paddedHex )

    //  Set up a buffer to hold the data
    const timeBuffer = new ArrayBuffer( 8 );
    const timeView   = new DataView( timeBuffer );

    //  Take the hex string, split it into 2-character chunks 
    const timeBytes = paddedHex.match( /.{1,2}/g ).map(
        //  Convert to bytes
        byte =&gt; parseInt( byte, 16 )
    );

    //  Write each byte into timeBuffer.
    for ( let i = 0; i &lt; 8; i++ ) {
         timeView.setUint8(i, timeBytes[i]);
    }
    console.log( "timeView is ",  new Uint8Array( timeView   ) );
    console.log( "timeBuffer is", new Uint8Array( timeBuffer ) );

    //  Use Web Crypto API to generate the HMAC key
    //  https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
    const key = await crypto.subtle.importKey(
        "raw",
        decodedSecret,
        { 
            name: "HMAC", 
            hash: algorithm 
        },
        false,
        ["sign"]
    );

    //  Sign the timeBuffer with the generated HMAC key
    //  https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign
    const signature = await crypto.subtle.sign( "HMAC", key, timeBuffer );

    //  Get HMAC as bytes
    const hmac = new Uint8Array( signature );
    console.log( "hmac is ", hmac );

    //  https://datatracker.ietf.org/doc/html/rfc4226#section-5.4
    //  Use the last byte to generate the offset
    const offset = hmac[ hmac.length - 1 ] &amp; 0x0f;
    console.log( "offset is " + offset )

    //  Bit Twiddling operations
    const binaryCode = 
        ( ( hmac[ offset     ] &amp; 0x7f ) &lt;&lt; 24 ) |
        ( ( hmac[ offset + 1 ] &amp; 0xff ) &lt;&lt; 16 ) |
        ( ( hmac[ offset + 2 ] &amp; 0xff ) &lt;&lt;  8 ) |
        ( ( hmac[ offset + 3 ] &amp; 0xff ) );

    //  Turn the binary code into a decimal string
    stringOTP = binaryCode.toString();
    console.log( "stringOTP is " + stringOTP );

    //  Count backwards from the last character for the length of the code
    otp = stringOTP.slice( -length) 
    console.log( "otp is " + otp );
    //  Pad with 0 to full length
    otp = otp.padStart( length, "0" );
    console.log( "padded otp is " + otp );

    //  All done!
    return otp;
}


// Generate a TOTP code
( async () =&gt; {
    console.log( await generateTOTP( "4FCDTLHR446DPFCKUA46UFIAYTQIDSZ2", 30, 6, "SHA-1" ) );
} )();
</code></pre>

<p>It works with the three specified algorithms, generating between 1 and 10 digits, and works with any positive integer interval. Not all combinations are sensible; <a href="https://shkspr.mobi/blog/2025/02/the-least-secure-totp-code-possible/">a one digit code valid for two minutes would be silly</a>. But it is up to you to use this responsibly.</p>

<p>I hope I've shown that you don't need to rely on 3rd party libraries to do your cryptography; you can do it all in the browser instead.</p>

<p>You can <a href="https://codeberg.org/edent/TOTP_Test_Suite">grab the code from Codeberg</a>.</p>

<div id="footnotes" role="doc-endnotes">
<hr>
<ol start="0">

<li id="fn:sigh">
<p><em>*sigh*</em> Please don't be the boring dolt who makes a joke about Top of The Pops. Yes, I know they share the same initialism. And, yes, it's funny how <code>nonce</code> means something different in cryptography compared to British English.&nbsp;<a href="https://shkspr.mobi/blog/2025/03/using-the-web-crypto-api-to-generate-totp-codes-in-javascript-without-3rd-party-libraries/#fnref:sigh" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=58536&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/03/using-the-web-crypto-api-to-generate-totp-codes-in-javascript-without-3rd-party-libraries/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Minimum Viable Clustered-Marker Globe using OpenFreeMap and MapLibre GL]]></title>
		<link>https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/</link>
					<comments>https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 14 Jan 2025 12:34:14 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[map]]></category>
		<category><![CDATA[OpenBenches]]></category>
		<category><![CDATA[OpenStreetMap]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=55240</guid>

					<description><![CDATA[I love OpenFreeMap it is a quick, easy, and free way to add beautiful maps to your Open Source projects.  With the latest release of MapLibre-GL I wanted to see if there was an easy way to use both to make an interactive globe with clustered markers.  Spoiler alert: yes!  Basic Globe  Here&#039;s a basic example which I&#039;ve trimmed down from this example.    When you load the below code, you&#039;ll get a…]]></description>
										<content:encoded><![CDATA[<p>I love <a href="https://openfreemap.org/">OpenFreeMap</a> it is a quick, easy, and <em>free</em> way to add beautiful maps to your Open Source projects.  With the latest release of <a href="https://maplibre.org/maplibre-gl-js/docs/">MapLibre-GL</a> I wanted to see if there was an easy way to use both to make an interactive globe with clustered markers.</p>

<p>Spoiler alert: yes!</p>

<h2 id="basic-globe"><a href="https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/#basic-globe">Basic Globe</a></h2>

<p>Here's a basic example which I've trimmed down from <a href="https://maplibre.org/maplibre-gl-js/docs/examples/globe-vector-tiles/">this example</a>.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/01/globe.webp" alt="A globe of the planet Earth with labels for countries." width="720" height="714" class="aligncenter size-full wp-image-55241">

<p>When you load the below code, you'll get a globe which you can spin and zoom. Nifty!</p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Globe Projection using OpenFreeMap&lt;/title&gt;
        &lt;meta charset="utf-8"&gt;
        &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
        &lt;link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5.0.0/dist/maplibre-gl.css"&gt;
        &lt;script src="https://unpkg.com/maplibre-gl@5.0.0/dist/maplibre-gl.js"&gt;&lt;/script&gt;
        &lt;style&gt;
            body { margin: 0;padding: 0; }
            html, body, #map { height: 100%; }
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div id="map"&gt;&lt;/div&gt;
        &lt;script type="module"&gt;
            const map = new maplibregl.Map({
                container: "map",
                style: "https://tiles.openfreemap.org/styles/liberty",
                zoom: 2,
                center: [0.123, 51.2345],
                pitch: 0,
                canvasContextAttributes: { antialias: true }
            });

            map.on('style.load', () =&gt; {
                map.setProjection({
                    type: 'globe',
                });
            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<h2 id="adding-markers"><a href="https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/#adding-markers">Adding Markers</a></h2>

<p>In most respects, this acts like a normal MapLibre GL map.  To add a marker, add this code:</p>

<pre><code class="language-js">const marker = new maplibregl.Marker()
    .setLngLat([12.345, 54.321])
    .addTo(map);
</code></pre>

<h2 id="geojson-clusters"><a href="https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/#geojson-clusters">GeoJSON Clusters</a></h2>

<p>Now for the big one!  On <a href="https://openbenches.org/">OpenBenches</a> we display markers and <em>clusters</em> of markers. On a flat map, it looks like this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2025/01/ob-clusters.webp" alt="Map of Europe. There are big circles on it saying how many markers are inside." width="655" height="655" class="aligncenter size-full wp-image-55243">

<p>We want to turn it into this beauty:</p>

<p></p><div style="width: 620px;" class="wp-video"><video class="wp-video-shortcode" id="video-55240-2" width="620" height="687" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2025/01/b169e7bb1cfc045f.mp4?_=2"><a href="https://shkspr.mobi/blog/wp-content/uploads/2025/01/b169e7bb1cfc045f.mp4">https://shkspr.mobi/blog/wp-content/uploads/2025/01/b169e7bb1cfc045f.mp4</a></video></div><p></p>

<p>Here's the code, <a href="https://maplibre.org/maplibre-gl-js/docs/examples/cluster/">adapted from the tutorial</a>:</p>

<pre><code class="language-js">const map = new maplibregl.Map({
    container: "map",
    style: "https://tiles.openfreemap.org/styles/liberty",
    zoom: 2,
    center: [0.123, 51.2345],
    pitch: 0,
    canvasContextAttributes: { antialias: true }
});

map.on('style.load', () =&gt; {
    map.setProjection({
        type: 'globe',
    });
});

//  Load GeoJSON
async function load_GeoJSON() {
    const response = await fetch( "geo.json" )
    var benches_json = await response.json();
    return benches_json;
}

//  Asynchronous function to add custom layers and sources
async function addCustomLayersAndSources() {
    //  Get the data
    var geo_data = await load_GeoJSON();
    //  Load the GeoJSON
    if (!map.getSource('geo_data')) {
        map.addSource('geo_data', {
            type: 'geojson',
            data: geo_data,
            cluster: true,
            clusterMaxZoom: 17, // Max zoom to cluster points on
            clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
        });
    }

    //  Custom point marker
    if ( map.listImages().includes("marker-icon") == false ) {
        var image = await map.loadImage('/marker.png');
        map.addImage('marker-icon', image.data);
    }

    //  Add the clusters
    if (!map.getLayer('clusters')) {
        map.addLayer({
            id: 'clusters',
            type: 'circle',
            source: 'geo_data',
            filter: ['has', 'point_count'],
            paint: {
                // Use step expressions (https://maplibre.org/maplibre-style-spec/expressions/#step)
                // with three steps to implement three types of circles:
                //   * Blue, 20px circles when point count is less than 100
                //   * Yellow, 30px circles when point count is between 100 and 750
                //   * Pink, 40px circles when point count is greater than or equal to 750
                'circle-color': [
                    'step', ['get', 'point_count'],
                        '#51bbd655',
                    100, '#f1f07555',
                    750, '#f28cb155'
                ],
                'circle-radius': [
                    'step', ['get', 'point_count'],
                        20,
                    100, 30,
                    750, 40
                ],
                'circle-stroke-width': [
                    'step', ['get', 'point_count'],
                        1,
                    100, 1,
                    750, 1
                ],
                'circle-stroke-color': [
                    'step', ['get', 'point_count'],
                        '#000',
                    100, '#000',
                    750, '#000'
                ],
            }
        });

        //  Show number of markers in each cluster
        map.addLayer({
            id: 'cluster-count',
            type: 'symbol',
            source: 'geo_data',
            filter: ['has', 'point_count'],
            layout: {
                'text-field': '{point_count_abbreviated}',
                'text-font': ['Noto Sans Regular'],
                'text-size': 25
            }
        });

        //  Show individual markers
        map.addLayer({
            id: 'unclustered-point',
            source: 'geo_data',
            filter: ['!', ['has', 'point_count']],
            type: 'symbol',
            layout: {
                "icon-overlap": "always",
                'icon-image': 'marker-icon',  // Use the PNG image
                'icon-size': .1              // Adjust size if necessary
            }
        });
    }
}

//  Start by drawing the map
map.on('load', async () =&gt; {
    await addCustomLayersAndSources();
});
</code></pre>

<p>Obviously, there's a lot more you can do - but I hope this shows just how quickly you can get a clustermap working on a globe.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=55240&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2025/01/minimum-viable-clustered-marker-globe-using-openfreemap-and-maplibre-gl/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		<enclosure url="https://shkspr.mobi/blog/wp-content/uploads/2025/01/b169e7bb1cfc045f.mp4" length="4330564" type="video/mp4" />

			</item>
		<item>
		<title><![CDATA[I can't use my number pad for 2FA codes]]></title>
		<link>https://shkspr.mobi/blog/2024/04/i-cant-use-my-number-pad-for-2fa-codes/</link>
					<comments>https://shkspr.mobi/blog/2024/04/i-cant-use-my-number-pad-for-2fa-codes/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 17 Apr 2024 11:34:20 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[ui]]></category>
		<category><![CDATA[ux]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50119</guid>

					<description><![CDATA[This has to be the most infuriating bug report I&#039;ve ever submitted.  I went to type in my 2FA code on a website - but no numbers appeared on screen. Obviously, I was an idiot and had forgotten to press the NumLock button. D&#039;oh! I toggled it on and typed again. No numbers appeared. I switched to another tab, my numbers appeared when I typed them. So I was reasonably confident that my keyboard was…]]></description>
										<content:encoded><![CDATA[<p>This has to be the most infuriating bug report I've ever submitted.</p>

<p>I went to type in my 2FA code on a website - but no numbers appeared on screen. Obviously, I was an idiot and had forgotten to press the NumLock button. D'oh! I toggled it on and typed again. No numbers appeared. I switched to another tab, my numbers appeared when I typed them. So I was reasonably confident that my keyboard was working.</p>

<p>I swapped back to the 2FA entry and tried again. Still nothing.  Then I tried typing the numbers using the number row on my keyboard. My 2FA code appeared.</p>

<p>WHAT IN THE SAINTED NAME OF ALPHONSE CHAPANIS IS GOING ON?!?!?</p>

<p>Developers often use JavaScript to "improve" the standard features of HTML.  For example, using <code>&lt;input type="number"&gt;</code> has some <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number#accessibility">accessibility concerns</a> and using <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/#aa-numeric"><code>inputmode="numeric"</code></a> is great for showing a number key board on mobile, but not much else.</p>

<p>So a developer wants a reliable way to make sure a user can <em>only</em> type numbers. Fair enough.</p>

<p>There are two ways to do this - a right way and a wrong way - using <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent"><code>KeyboardEvent</code></a>.</p>

<p>One way is to <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">listen for the character being sent from the keyboard</a> - known as the <code>key</code>.</p>

<p>The other is to <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code">listen for the <em>button</em> being pressed on the keyboard</a> - known as the <code>code</code>.</p>

<p>A good demo of this is at <a href="https://keyjs.dev/">keyjs.dev</a> - play around with it to see what keyboard buttons your browser can detect.</p>

<p>When I press 7 on the top row of my keyboard, the key is 7 and the code is <strong><code>Digit7</code></strong>.</p>

<p>But when I press 7 on my number pad, the key is 7 but the code is <strong><code>Numpad7</code></strong>.</p>

<p>The JavaScript on the website was rejecting any key code which wasn't a "Digit"!</p>

<p>Perhaps I am a weirdo for insisting on both having and using my numpad?  Perhaps developers need to test on something other than MacBooks? Perhaps JavaScript was a mistake and the Web would be better without it?</p>

<p>Either way, don't be like that website. Let users type in using whatever keys they like.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50119&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/04/i-cant-use-my-number-pad-for-2fa-codes/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[What's the window size of a background tab?]]></title>
		<link>https://shkspr.mobi/blog/2021/09/whats-the-window-size-of-a-background-tab/</link>
					<comments>https://shkspr.mobi/blog/2021/09/whats-the-window-size-of-a-background-tab/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 04 Sep 2021 11:06:12 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[chrome]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[HTML5]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=40166</guid>

					<description><![CDATA[Whenever I open Twitter in a new tab on my phone, the page layout looks weird for a few seconds. It starts out looking like the desktop view and then, after a few seconds, it  snaps back to the mobile view.  What&#039;s causing this?  Try opening this link to a window size detector in a background tab. Then visit that tab.  On Chrome, this is what I see.    If I hit the refresh button on that tab, the …]]></description>
										<content:encoded><![CDATA[<p>Whenever I open Twitter in a new tab on my phone, the page layout looks weird for a few seconds. It starts out looking like the desktop view and then, after a few seconds, it  snaps back to the mobile view.</p>

<p>What's causing this?</p>

<p>Try opening <a href="https://www.rapidtables.com/web/tools/window-size.html">this link to a window size detector</a> in a background tab. Then visit that tab.</p>

<p>On Chrome, this is what I see.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/09/zero-size.png" alt="The Outer window size is zero by zero." width="400" class="aligncenter size-full wp-image-40169">

<p>If I hit the refresh button on that tab, the  Outer window size snaps back:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/09/full-size.png" alt="The Outer window size is 1413 by 768." width="400" class="aligncenter size-full wp-image-40168">

<p>What's going on?</p>

<p>According to the specification:</p>

<blockquote><p><a href="https://drafts.csswg.org/cssom-view/#dom-window-outerwidth">The <code>outerWidth</code> attribute must return the width of the client window. If there is no client window this attribute must return zero.</a></p></blockquote>

<p>This is where I get confused.  The <em>inner</em> width &amp; height are the amount of space the the browser has to display web content - so ignores the browser bar, menu bars, window decorations etc. The <em>outer</em> width &amp; height are the total amount of space the browser window has.</p>

<p>How can a browser window have no outer size, but simultaneously have an inner size???!?!</p>

<video class="aligncenter" id="video-40166-1" loop="1" autoplay="1" preload="metadata" muted="" width="268" height="250">
   <source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2021/09/bigger-on-the-inside.mp4?_=1">
</video>

<p>There <em>is</em> a client window. The tab may be rendering away in the background - but its parent still has a non-zero size.</p>

<p>There has been a <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=719296">bug report open on Chrome about this since <strong>2017</strong></a>.</p>

<p>Firefox takes a different approach. On desktop, both outer and inner are reported correctly for background tabs. On mobile, both outer and inner dimensions are set to zero.  This may change as it is a <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1579584">potential fingerprinting target</a>.</p>

<p>So, web developers, please don't rely on <code>window.innerHeight</code> and <code>window.outerHeight</code> - users may be opening your site in the background, which causes the browser to lie to you.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=40166&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/09/whats-the-window-size-of-a-background-tab/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Just because I have a vertical screen, doesn't mean I'm on a phone!]]></title>
		<link>https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/</link>
					<comments>https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 02 Feb 2021 12:42:26 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=37926</guid>

					<description><![CDATA[I&#039;m a weirdo - I fully admit that. As part of my home working set up, I use a vertical monitor. I read and write a lot of long documents - and this form factor suits me perfectly.    I&#039;ve been doing this for a long time. It is a natural part of my workflow.  For anything longer than an email, it&#039;s the perfect orientation. Most Linux apps work just fine like this - although menu buttons tend to…]]></description>
										<content:encoded><![CDATA[<p>I'm a weirdo - I fully admit that. As part of my home working set up, I use a vertical monitor. I read and write a lot of long documents - and this form factor suits me perfectly.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2020/04/Vertical-Screen-showing-YouTube.jpg" alt="Vertical Screen showing YouTube." width="255" height="340" class="alignleft size-full wp-image-34758">

<p>I've been doing this <a href="https://twitter.com/edent/status/1206586972">for a long time</a>. It is a natural part of my workflow.  For anything longer than an email, it's the perfect orientation. Most Linux apps work just fine like this - although menu buttons tend to hide behind overflows.</p>

<p>Websites though, ah! That's where the problem begins.  Lots of websites think "Vertical Screen == Mobile User"!</p>

<p>This is a minor problem - and one of my own making - but I thought it worth explaining the problems it brings, why it occurs, &amp; how to stop it.</p>

<h3 id="examples"><a href="https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#examples">Examples</a></h3>

<p>Here are some typical websites viewed on my 24″ vertical monitor. On the left if how it renders, and on the right how it <em>should</em> render.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/02/montje50.jpg" alt="The Just Eat website." width="705" height="603" class="aligncenter size-full wp-image-38009">

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/02/montgu50.jpg" alt="Guardian News website." width="705" height="603" class="aligncenter size-full wp-image-38010">

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/02/montps50.jpg" alt="Popular science website." width="705" height="603" class="aligncenter size-full wp-image-38011">

<h2 id="problems"><a href="https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#problems">Problems</a></h2>

<p>Generally, there are several problems I encounter:</p>

<ul>
<li>Navigation is hidden behind a burger / ☰ menu.</li>
<li>Some elements, like carousels, only work with touchscreen controls.</li>
<li>Images are served as low resolution, for 6 inch screens and then blown up to 24 inches.</li>
<li>Lots of wasted space taken up with "hero images" and  finger-friendly buttons.</li>
<li>Some content simply not available to mobile users.</li>
</ul>

<h2 id="why-this-occurs"><a href="https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#why-this-occurs">Why this occurs</a></h2>

<p>My monitor's native resolution is 1080x1920.  But I find the fonts slightly too small at that resolution - given how far I sit from the screen. I use Pop_OS Linux, which lets me scale the fonts rather than the screen resolution.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/01/Screenshot-from-2021-01-25-15-09-23.png" alt="Gnome Tweaks screenshot showing a font scaling factor." width="1044" height="700" class="aligncenter size-full wp-image-37933">

<p>A scale factor of 1.5 translates to an effective screen resolution of 720x1280.</p>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent">User Agent "sniffing" is considered an antipattern</a> and is discouraged. So most websites don't bother to check if I'm browsing on an iPhone or Android, instead, they use JavaScript or CSS to get my screen resolution.</p>

<p>They - somewhat reasonably - see a 720p screen in a vertical orientation and assume it is a small screen device.</p>

<h2 id="how-to-tackle-this-user-side"><a href="https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#how-to-tackle-this-user-side">How to tackle this (user side)</a></h2>

<p>Zoom out! That's the obvious answer. If I hit <kbd>CTRL</kbd>+<kbd>-</kbd> 3 times, my resolution becomes 1080x1920. But that can leave some sites too small to read properly. I need to zoom out the page, and zoom in the font.</p>

<p>I have tried "Fractional Scaling" - it works OK on Wayland, but leaves all the fonts looking soft and fuzzy.</p>

<p>So I've set my font scaling to 1.36, which gives me a resolution of 864x1536 - which is enough to stop most sites assuming I'm on a tiny mobile phone.</p>

<p>But this shouldn't be my problem to solve.</p>

<h2 id="how-to-tackle-this-website-side"><a href="https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/#how-to-tackle-this-website-side">How to tackle this (website side)</a></h2>

<p>STOP NAÏVELY USING SCREEN RESOLUTION!</p>

<p>OK, there's no way to get the <em>physical</em> size of a user's screen. That functionality just doesn't exist in either JavaScript or CSS.</p>

<p>But you can get the Dots Per Inch (DPI). Well, sort of...</p>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/resolution">CSS allows you to get the DPI of the screen</a>.</p>

<p>If I go to Lea Verou's <a href="https://dpi.lv/"></a><a href="https://DPI.lv/">https://DPI.lv/</a> I see that my monitor's resolution is correctly detected as 1080x1920!  No matter what zoom level or font scaling I use - it is <em>always</em> the correct resolution.</p>

<pre><code class="language-js">var dppx = window.devicePixelRatio;
var screenWidth  = screen.width  * dppx;
var screenHeight = screen.height * dppx;
</code></pre>

<p>That's how you get the <em>real</em> resolution, unencumbered by whatever the OS is doing to the scaling.</p>

<p>If I go to <a href="https://www.infobyip.com/detectmonitordpi.php">a different DPI detector</a>, I can see exactly how many pixels there are per inch.  My vertical monitor is detected as 120px/inch.</p>

<p>This isn't <em>quite</em> right. And different browser engines calculate this differently. On Linux, I got these results with the three main rendering engines:</p>

<ul>
<li>Chrome: 5.14px/mm.</li>
<li>Firefox: 4.73px/mm.</li>
<li>Webkit: 3.78px/mm.</li>
</ul>

<p>When I physically measure the screen, it's about 3.62px/mm!</p>

<p>Obviously that's not incredibly accurate - but it is useful in giving a web developer a <em>rough</em> idea of physical screen size.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=37926&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/02/just-because-i-have-a-vertical-screen-doesnt-mean-im-on-a-phone/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Please stop using CDNs for external Javascript libraries]]></title>
		<link>https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/</link>
					<comments>https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 11 Oct 2020 11:37:59 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=36891</guid>

					<description><![CDATA[I want to discuss a (minor) antipattern that I think is (slightly) harmful.  Lots of websites use large Javascript libraries. They often include them by using a 3rd party Content Delivery Network like so:  &#60;script src=&#34;https://cdn.example.com/js/library-v1.2.3.js&#34;&#62;&#60;/script&#62;   There are, supposedly, a couple of advantages to doing things this way.   Users may already have the JS library in their…]]></description>
										<content:encoded><![CDATA[<p>I want to discuss a (minor) antipattern that I think is (slightly) harmful.</p>

<p>Lots of websites use large Javascript libraries. They often include them by using a 3rd party Content Delivery Network like so:</p>

<pre><code class="language-html">&lt;script src="https://cdn.example.com/js/library-v1.2.3.js"&gt;&lt;/script&gt;
</code></pre>

<p>There are, supposedly, a couple of advantages to doing things this way.</p>

<ol>
<li>Users may already have the JS library in their cache from visiting another site.</li>
<li>Faster download speeds of large libraries from CDNs.</li>
<li>Latest software versions automatically when the CDN updates.</li>
</ol>

<p>I think these advantages are overstated and lead to some significant disadvantages.</p>

<h2 id="cacheing"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#cacheing">Cacheing</a></h2>

<p>I get the superficial appeal of this. But there are dozens of popular Javascript CDNs available. What are the chances that your user has visited a site which uses the <em>exact</em> same CDN as your site?</p>

<p>How much of an advantage does that really give you?</p>

<h2 id="speed"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#speed">Speed</a></h2>

<p>You probably shouldn't be using multi-megabyte libraries. Have some respect for your users' download limits. But if you are truly worried about speed, surely your whole site should be behind a CDN - not just a few JS libraries?</p>

<h2 id="versioning"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#versioning">Versioning</a></h2>

<p>There are some CDN's which let you include the latest version of a library. But then you have to deal with breaking changes with little warning.</p>

<p>So most people only include a specific version of the JS they want. And, of course, if you're using v1.2 and another site is using v1.2.1 the browser can't take advantage of cacheing.</p>

<h2 id="reliability"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#reliability">Reliability</a></h2>

<p>Is your CDN reliable? You hope so! But if a user's network blocks a CDN or interrupts the download, you're now serving your site without Javascript.  That isn't necessarily a bad thing - you do progressive enhancement, right? But it isn't ideal.</p>

<p>If you serve your JS from the same source as your main site, there is less chance of a user getting a broken experience.</p>

<h2 id="privacy"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#privacy">Privacy</a></h2>

<p>What's your CDN's privacy policy? Do you need to tell your user that their browsing data are being sent to a shadowy corporation in a different legal jurisdiction?</p>

<p>What is your CDN doing with all that data?</p>

<h2 id="security"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#security">Security</a></h2>

<p>British Airways' payments page was <a href="https://www.theregister.com/2018/09/11/british_airways_website_scripts/">hacked by compromised 3rd party Javascript</a>.  A malicious user changed the code on site which wasn't in BA's control - then BA served it up to its customers.</p>

<p>What happens if someone hacks your CDN?</p>

<p>You gain extra security by using <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">SubResource Integrity</a>.  That lets you write code like:</p>

<pre><code class="language-html">&lt;script src="https://cdn.example.com/js/library-v1.2.3.js"
   integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
   crossorigin="anonymous"&gt;&lt;/script&gt;
</code></pre>

<p>If even a single byte of that JS file is changed, the hash won't match and the browser should refuse to run the code.</p>

<p>Of course, that means that you could end up with a broken experience on your site. So just serve the JS from your own site.</p>

<h2 id="so-what"><a href="https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/#so-what">So what?</a></h2>

<p>This isn't the biggest issue on the web. And I'm certainly guilty of misusing CDNs like this.</p>

<p>Back when there were only a few CDNs, and their libraries didn't change rapidly, there was an advantage to using them.</p>

<p>Nowadays, in an era of rampant privacy and security violations, I think using 3rd party sources for Javascript should be treated as an anti-pattern.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=36891&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2020/10/please-stop-using-cdns-for-external-javascript-libraries/feed/</wfw:commentRss>
			<slash:comments>36</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Major sites running unauthenticated JavaScript on their payment pages]]></title>
		<link>https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/</link>
					<comments>https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 29 Nov 2018 12:39:41 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[hack]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[Responsible Disclosure]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[sri]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=30747</guid>

					<description><![CDATA[A few months ago, British Airways&#039; customers had their credit card details stolen.  How was this possible?  The best guess goes something like this:   BA had 3rd party JS on its payment page &#60;script src=&#34;https://example.com/whatever.js&#34;&#62;&#60;/script&#62; The 3rd party&#039;s site was hacked, and the JS was changed. BA&#039;s customers ran the script, which then harvested their credit card details as they were…]]></description>
										<content:encoded><![CDATA[<p>A few months ago, British Airways' customers had their credit card details stolen.  How was this possible?  The <a href="https://www.theregister.co.uk/2018/09/12/feedify_magecart_javascript_library_hacked/">best guess goes something like this</a>:</p>

<ol>
<li>BA had 3rd party JS on its payment page <br><code>&lt;script src="https://example.com/whatever.js"&gt;&lt;/script&gt;</code></li>
<li>The 3rd party's site was hacked, and the JS was changed.</li>
<li>BA's customers ran the script, which then harvested their credit card details as they were typed in.</li>
</ol>

<p>This should have been a wake-up call to the industry. Don't load unauthenticated code on your website - and especially not on your payments page.</p>

<p>If you absolutely have to load someone else's code, check to see if it has been altered.  This is done using <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">SubResource Integrity</a> (SRI).</p>

<p>SRI tells the user's browser to check that the code hasn't been changed since the website was published. It looks like this:</p>

<pre><code class="language-html">&lt;script src="https://example.com/whatever.js"
        integrity="sha384-eP2mZH+CLyffr1fGYsgMUWJFzVwB9mkUplpx9Y2Y3egTeRlmzD9suNR+56UHKr7v" 
        crossorigin="anonymous"&gt;&lt;/script&gt;
</code></pre>

<p>If even a single bit of the code has changed since it was added to the page, the browser refuses to run it.</p>

<h2 id="who-isnt-using-this"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#who-isnt-using-this">Who isn't using this</a></h2>

<h3 id="deliveroo"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#deliveroo">Deliveroo</a></h3>

<p>Gig-economy food flingers add in code from <a href="https://cdnjs.com/">CDNJS</a>.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/deliveroo2-fs8.png" alt="HTML source for Deliveroo's payment page." width="758" height="374" class="aligncenter size-full wp-image-30748"></p>

<p>What's especially annoying about this, is that the CDNJS website has a "one-click copy" for SRI.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/cdnjs-fs8.png" alt="A drop-down menu with a highlight on &quot;Click to copy SRI&quot;." width="669" height="286" class="aligncenter size-full wp-image-30750"></p>

<h3 id="spotify"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#spotify">Spotify</a></h3>

<p>Their payment page loads code from <code>live.adyen.com</code>
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/Spotify-fs8.png" alt="HTML code from Spotify." width="769" height="162" class="aligncenter size-full wp-image-30751">
Adyen are their payment provider - so if they get hacked, credit card details are going to get compromised. But how much easier is it for an attacker to subtly change their JavaScript than to hack their entire mainframe?</p>

<h3 id="the-guardian"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#the-guardian">The Guardian</a></h3>

<p>Despite being a tofu-knitting member of the bourgeoisie, I am yet to subscribe to teh Gruan.  If I did, I'd risk their affiliate tracker going rogue and stealing my organic credit card details.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/Guardian-fs8.png" alt="HTML source of the Guardian's website." width="649" height="127" class="aligncenter size-full wp-image-30756">
Bonus points for leaving a handy pointer to their internal Google docs...</p>

<h3 id="fanduel"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#fanduel">Fanduel</a></h3>

<p>Sports betting site running unverified scripts from external sources.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/Fanduel-fs8.png" alt="HTML source for FanDuel." width="639" height="206" class="aligncenter size-full wp-image-30752">
They've also got external style-sheets</p>

<pre><code class="language-html">&lt;link rel="stylesheet" href="//d2avoc1xjbdrch.cloudfront.net/6.26.0/styles/desktop.css"&gt;
</code></pre>

<p>If an attacker can change the JS or CSS, they could compromise users of the site.</p>

<h3 id="easyjet"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#easyjet">EasyJet</a></h3>

<p>I feel a bit conflicted about this one.  You can <em>probably</em> trust Google not to get hacked. <a href="https://thehackernews.com/2018/10/google-plus-shutdown.html">Right</a>?
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/EasyJet-fs8.png" alt="HTML source of EasyJet's website." width="556" height="94" class="aligncenter size-full wp-image-30758"></p>

<p>Google supports SRI - but <a href="https://developers.google.com/speed/libraries/#jquery">doesn't mention it anywhere on their Hosted Libraries site</a>.</p>

<h3 id="british-airways"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#british-airways">British Airways!</a></h3>

<p>Yup! They've not learned their lesson. <strong>Three</strong> pieces of unverified code running on the payment page.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/BA-fs8.png" alt="HTML code." width="824" height="204" class="aligncenter size-full wp-image-30800"></p>

<ul>
<li>Maxymiser is an A/B testing and analytics tool. Run by Oracle now. Most ad-blockers prevent it loading.</li>
<li>Google's reCAPTCHA. If that gets hacked, half the planet is compromised.</li>
<li><a href="https://www.globalsign.com/en/ssl/secure-site-seal/">SiteSeal</a> "proves" your site is secure by displaying a image. No, I don't understand that either.</li>
</ul>

<p></p><div style="width: 610px" class="wp-caption alignnone"><img src="https://shkspr.mobi/blog/wp-content/uploads/2018/11/seal_125-50_blue.png" alt="An SSL badge which proves nothing." width="125" height="50" class="aligncenter size-full wp-image-54278"><p class="wp-caption-text">This does not make the site magically secure.</p></div><p></p>

<p>All three of them are highly trustworthy. But if you're BA and you've already been bitten by bad security practices, doesn't it make sense to go full "belt-and-braces"?</p>

<h3 id="and-more"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#and-more">...and more?</a></h3>

<p>These are just a small sample of the sites I've found. <a href="https://www.w3.org/TR/SRI/">SRI has been available for two years</a> and it still isn't being used enough.</p>

<h2 id="responsible-disclosure"><a href="https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/#responsible-disclosure">Responsible Disclosure</a></h2>

<p>I've reported this issue to a few sites by using responsible-disclosure aggregator <a href="https://hackerone.com/edent">HackerOne</a>.</p>

<p>Typically, my warning goes unheeded with a response like:</p>

<blockquote><p>Based on your initial description, there do not appear to be any security implications as a direct result of this behavior, this is an Informational issue at best, unless you can prove those third-party domains can be compromised in any way.</p></blockquote>

<p>or</p>

<blockquote><p>This appears to be more of a risk acceptance rather than a vulnerability. Although there is no PoC for this report, I will forward the information to the customer and see where to go from there.</p></blockquote>

<p>That's fair enough. I'm not expecting a huge payout and it is <em>only</em> an informative report; I can't prove that the external sites are vulnerable.  But there really ought to be a concerted effort to make payment sites as secure as possible.</p>

<p>This needs to be taken seriously. If you're handling users' details, you need to take every possible step to keep them secure.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=30747&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2018/11/major-sites-running-unauthenticated-javascript-on-their-payment-pages/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Redirect GitHub ID to Username]]></title>
		<link>https://shkspr.mobi/blog/2018/11/redirect-github-id-to-username/</link>
					<comments>https://shkspr.mobi/blog/2018/11/redirect-github-id-to-username/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 22 Nov 2018 12:01:00 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[hack]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=30651</guid>

					<description><![CDATA[Scratching my own itch here...  GitHub users have a username (mine is @edent) and have a user ID number (mine is #837136).  If you want to redirect a user ID to a username, you can use the little service I&#039;ve cobbled together:  https://edent.github.io/github_id/#837136  That will take your browser to my GitHub page, using nothing but my ID.  Why?   Some login services only give you the GitHub…]]></description>
										<content:encoded><![CDATA[<p>Scratching my own itch here...</p>

<p>GitHub users have a username (mine is <code>@edent</code>) <em>and</em> have a user ID number (mine is <code>#837136</code>).</p>

<p>If you want to redirect a user ID to a username, you can use the little service I've cobbled together:</p>

<p><a href="https://edent.github.io/github_id/#837136">https://edent.github.io/github_id/#837136</a></p>

<p>That will take your browser to my GitHub page, using nothing but my ID.</p>

<h2 id="why"><a href="https://shkspr.mobi/blog/2018/11/redirect-github-id-to-username/#why">Why?</a></h2>

<ul>
<li>Some login services only give you the GitHub user's ID.</li>
<li>GitHub users can change their username - but their ID stays the same.</li>
</ul>

<h2 id="how"><a href="https://shkspr.mobi/blog/2018/11/redirect-github-id-to-username/#how">How?</a></h2>

<p>Inspired by <a href="http://caius.name/">Caius Durling</a>'s useful <a href="http://caius.github.io/github_id/">GitHub username to ID</a> service.</p>

<p>Mine is a scrap of JavaScript which uses <a href="https://api.github.com/user/837136">this undocumented endpoint</a> to get user info.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2018/10/GitHub-API-fs8.png" alt="A screen of JSON code showing my details." width="596" height="354" class="aligncenter size-full wp-image-30652">

<pre><code class="language-javascript">&lt;script type="text/javascript" charset="utf-8"&gt;
function get_github_name_for(id) {
    $.getJSON('https://api.github.com/user/' + id + "?callback=?", 
    function(json){
        var github_user_url = json["data"]["html_url"]
        window.location.replace(github_user_url)
        return false
    });
}
$(document).ready(function() {
    if (document.location.hash) {
        var id = document.location.hash.match(/^#(.*?)$/)[1]
        get_github_name_for(id)
    }
});
&lt;/script&gt;
</code></pre>

<p>Feel free to copy it, or use <a href="https://edent.github.io/github_id/#837136">https://edent.github.io/github_id/#837136</a> as a handy little webservice.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=30651&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2018/11/redirect-github-id-to-username/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[This SVG always shows today's date]]></title>
		<link>https://shkspr.mobi/blog/2018/02/this-svg-always-shows-todays-date/</link>
					<comments>https://shkspr.mobi/blog/2018/02/this-svg-always-shows-todays-date/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 25 Feb 2018 16:07:13 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[graphics]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[svg]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=29122</guid>

					<description><![CDATA[For my contact page, I wanted a generic calendar icon to let people view my diary.  Calendar icons are almost always a skeuomorph of a paper calendar, but I wondered if I could make it slightly more useful by creating a dynamic icon.  Here it is, an SVG calendar which always display&#039;s today&#039;s date:    The background image is derived from the Twitter TweMoji Calendar icon - CC-BY.  Text support in …]]></description>
										<content:encoded><![CDATA[<p>For <a href="https://edent.tel/">my contact page</a>, I wanted a generic calendar icon to let people view my diary.  Calendar icons are almost always a skeuomorph of a paper calendar, but I wondered if I could make it slightly more useful by creating a <em>dynamic</em> icon.</p>

<p>Here it is, <a href="https://shkspr.mobi/svg/calendar.svg">an SVG calendar which always display's today's date</a>:</p>

<iframe style="background: #fff; border:none;" src="https://shkspr.mobi/svg/calendar.svg" width="256px" height="256px"></iframe>

<p><small>The background image is derived from the <a href="https://github.com/twitter/twemoji/blob/gh-pages/2/svg/1f4c5.svg">Twitter TweMoji Calendar icon</a> - CC-BY.</small></p>

<p>Text support in SVG is a little awkward, so let me explain how I did this.</p>

<p>SVG supports JavaScript. This will run as soon as the image is loaded.</p>

<pre><code class="language-svg">&lt;svg onload="init(evt)" xmlns="http://www.w3.org/2000/svg"
aria-label="Calendar" role="img" viewBox="0 0 512 512"&gt;
</code></pre>

<p>Next step is to get the various date strings. I'm using the <code>en-GB</code> locale as that's where I'm based.</p>

<pre><code class="language-svg">&lt;script type="text/ecmascript"&gt;&lt;![CDATA[
function init(evt) {
  var time = new Date();
  var locale = "en-gb";
</code></pre>

<p>I want to display something like "Sunday 25 FEB" - the locale options allow for short and long names. So you could have "SUN 25 February".</p>

<pre><code class="language-js">  var DD   = time.getDate();
  var DDDD = time.toLocaleString(locale, {weekday: "long"});
  var MMM = time.toLocaleString(locale,  {month:   "short"});
</code></pre>

<p>Finally, we need to add the text on to the image.</p>

<pre><code class="language-js">  var svgDocument = evt.target.ownerDocument;

  var dayNode = svgDocument.createTextNode(DD);
  svgDocument.getElementById("day").appendChild(dayNode);

  var weekdayNode = svgDocument.createTextNode(DDDD);
  svgDocument.getElementById("weekday").appendChild(weekdayNode);

  var monthNode = svgDocument.createTextNode(MMM.toUpperCase());
  svgDocument.getElementById("month").appendChild(monthNode);

}
]]&gt;&lt;/script&gt;
</code></pre>

<p>Text positioning is relatively simplistic.  An X &amp; Y position which is anchored to the <em>bottom</em> of the text - remember that letters with descenders like <code>g</code> will extend beyond the bottom of the Y co-ordinate.  This is also where we set the colour of the text, its size, and a font.</p>

<p>A monospace font makes it easier to predict the layout.</p>

<pre><code class="language-svg">&lt;text id="month"
  x="32" 
  y="164" 
  fill="#fff" 
  font-family="monospace"
  font-size="140px"
  style="text-anchor: left"&gt;&lt;/text&gt;
</code></pre>

<p>A word on anchoring.  To centre the anchor, use <code>style="text-anchor: middle"</code></p>

<p>A quick test shows that this works on all desktop browsers and Android browsers. I've not tested on iPhones or anything more exotic.</p>

<p>Enjoy!</p>

<hr>

<ul>
<li><a href="https://github.com/edent/Dynamic-SVG-Calendar-Icon">GitHub repo</a></li>
<li><a href="https://news.ycombinator.com/item?id=16459550">Discussion on HackerNews</a></li>
</ul>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=29122&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2018/02/this-svg-always-shows-todays-date/feed/</wfw:commentRss>
			<slash:comments>20</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Using canvas to shrink images for Google Cloud Vision]]></title>
		<link>https://shkspr.mobi/blog/2018/01/using-canvas-to-shrink-images-for-google-cloud-vision/</link>
					<comments>https://shkspr.mobi/blog/2018/01/using-canvas-to-shrink-images-for-google-cloud-vision/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 08 Jan 2018 12:11:36 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[HTML5]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=29017</guid>

					<description><![CDATA[I&#039;ve started using Google Cloud Vision for running text detection on OpenBenches images.  There&#039;s just one problem - Google limits the size of the files that it will accept to 4MB.  Why? Who knows!  Obviously, it&#039;s easy to shrink an image server-side, but how do we do it in the browser?  First, let&#039;s take a bog-standard file chooser and add a &#60;canvas&#62; element.  &#60;input id=&#34;userFile&#34; type=&#34;file&#34;…]]></description>
										<content:encoded><![CDATA[<p>I've started using <a href="https://cloud.google.com/vision/">Google Cloud Vision</a> for running text detection on <a href="https://openbenches.org">OpenBenches</a> images.  There's just one problem - Google limits the size of the files that it will accept to 4MB.</p>

<p>Why? Who knows!</p>

<p>Obviously, it's easy to shrink an image server-side, but how do we do it in the browser?</p>

<p>First, let's take a bog-standard file chooser and add a <code>&lt;canvas&gt;</code> element.</p>

<pre><code class="language-html">&lt;input id="userFile" type="file" accept="image/jpeg" /&gt;
&lt;canvas id="shrink"&gt;&lt;/canvas&gt;
</code></pre>

<p>Next, when the user chooses a file, draw it on the canvas at half the resolution of the original file.</p>

<pre><code class="language-js">var input = document.getElementById('userFile');
input.addEventListener('change', drawImage, false);

function drawImage(e) {
   // The File
   var file = e.target.files[0];
   var reader = new FileReader();

   // The Canvas
   var canvas = document.getElementById("shrink");
   var ctx = canvas.getContext('2d');

   // The Image
   var img = new Image();

   // Once the file has been read, set it as the image's source
   reader.readAsDataURL(file);
   reader.onloadend = function () {
      img.src = reader.result;
   }

   // Once the image has been set, draw it on the canvas
   img.onload = function() {
      // Scale the canvas to be half the size of the image
      // You can change the "/2" to be whatever size you want
      ctx.canvas.width  = img.width /2;
      ctx.canvas.height = img.height /2;

      // Draw the image onto the canvas
      ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
   }
}
</code></pre>

<p>Here's the clever part! We can take the 2D image on the canvas and turn <em>that</em> into a JPG!  In this specific example, the quality is set to 75% - which reduces the files size still further.  This uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL"><code>.toDataURL()</code></a>.</p>

<pre><code class="language-js">var smallImage = canvas.toDataURL('image/jpeg', 0.75);
</code></pre>

<p>Now <code>smallImage</code> will contain <code>"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDA...."</code></p>

<p>A much smaller file than the original.</p>

<p>This representation of the file can then be <code>POST</code>ed to Google Cloud Vision.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=29017&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2018/01/using-canvas-to-shrink-images-for-google-cloud-vision/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[No Javascript Day]]></title>
		<link>https://shkspr.mobi/blog/2013/11/no-javascript-day/</link>
					<comments>https://shkspr.mobi/blog/2013/11/no-javascript-day/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 08 Nov 2013 12:00:47 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[Web Development]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=8860</guid>

					<description><![CDATA[I&#039;d like to propose that web designers around the world spend one day this year browsing the web with JavaScript disabled.  I&#039;m tentatively calling this &#34;International No Javascript UseR Experience Day&#34; or INJURED for short.  A few weeks ago, a reader of my blog complained that all they saw was a blank screen. As Liz Conlan pointed out, my CSS was making the whole page invisible.  My WordPress…]]></description>
										<content:encoded><![CDATA[<p>I'd like to propose that web designers around the world spend one day this year browsing the web with JavaScript disabled.</p>

<p>I'm tentatively calling this "International No Javascript UseR Experience Day" or INJURED for short.</p>

<p>A few weeks ago, a reader of my blog <a href="https://news.ycombinator.com/item?id=6604555">complained that all they saw was a blank screen</a>. As <a href="https://twitter.com/lizconlan/status/393348112963883008">Liz Conlan pointed out</a>, my CSS was making the whole page invisible.  My WordPress theme has a feature which renders the page blank until all the extra fonts etc have properly loaded - then it makes the page visible.  That's a neat little hack to stop the page jumping around as it loads - but it fails utterly when the user doesn't have JavaScript.</p>

<h2 id="why-do-this"><a href="https://shkspr.mobi/blog/2013/11/no-javascript-day/#why-do-this">Why Do This?</a></h2>

<p>Firstly, let's ask <strong>how many</strong> people browse the web without JS.</p>

<p>According to this <a href="http://digital.cabinetoffice.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/">recent post by the UK Government's web team</a>, approximately 1.1% of their visitors don't or can't use JavaScript.
<a href="http://digital.cabinetoffice.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/"><img src="https://shkspr.mobi/blog/wp-content/uploads/2013/10/No-JavaScript-fs8.png" alt="No JavaScript-fs8" width="525" height="241" class="aligncenter size-full wp-image-8864"></a></p>

<p>That's a small fraction of visitors - but not an insignificant number of people to piss off if you run a major website.</p>

<p>Secondly, <em>why</em> don't people use JavaScript?  There are a variety of reasons, most of which seem to fall into the following categories:</p>

<ul>
    <li>Security. Why run untrusted code on your computer if not strictly necessary?</li>
    <li>Speed. On slower machines, or those with limited bandwidth, JavaScript can cause a page to render slowly.</li>
    <li>Privacy. JavaScript is often used to track users.</li>
    <li>Accessibility. Not all users have perfect vision or dexterity - JavaScript can be an annoyance, assuming their accessible browser supports it at all.</li>
    <li>Incompatible browser. As well as specialised browsers, many people still run outdated versions of popular web browsers.</li>
    <li>Network interference. Some ISPs and corporate networks will automatically degrade or disable JavaScript.</li>
</ul>

<p>So, we have a bunch of customers / users who either can't or won't use JavaScript.  Should we ignore them?</p>

<h2 id="how-the-other-half-live"><a href="https://shkspr.mobi/blog/2013/11/no-javascript-day/#how-the-other-half-live">How The Other Half Live</a></h2>

<p>I think it's important to see how our technology works when presented in less-than-ideal circumstances.  If an astronaut on the ISS wants to load your website on their IE6 box running over slow link with huge ping times, what's their experience going to be?</p>

<p>I quite often use <a href="http://lynx.isc.org/current/">Lynx to browse the web</a>.  It's a text only browser with very few bells and whistles.  Here's how it looks when trying to use Twitter.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/10/Lynx-Twitter-fs8.png" alt="Lynx Twitter-fs8" width="722" height="457" class="aligncenter size-full wp-image-8865">
I use Lynx for 4 main reasons:</p>

<ol>
    <li>How does my layout work for those who use Text-To-Speech to browse the web? It's not perfect, but I can see how easy it is to navigate, whether I've used alt/title tags for images correctly.</li>
    <li>When on a painfully slow connection. We've all be stuck somewhere with Internet speeds barely better than dial-up, right? With Lynx I'm not loading MBs of JS, CSS, images, Flash objects, HTML5 video.  Just the information I want.</li>
    <li>What does a search engine see?  When a spider comes crawling, how much semantic meaning do they derive from my markup?</li>
    <li>Does basic functionality work for the lowest common denominator.</li>
</ol>

<p>It's amazing how quick the web when you're ignoring everything other than the HTML!  Moreso, it's incredible how even some simple sites won't work without JavaScript.</p>

<p>I'm not proposing that we go back to a text-only existence, nor that we all disable JavaScript permanently.  Merely that - once in a while - we experience the web without our favourite toy.  Do our sites break catastrophically? Is there a more accessible way we can provide a feature?</p>

<p>I'm not proposing to make a website, logo, or define a specific day.  I'd just like you to turn off JavaScript for a day and see if anything odd happens.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=8860&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2013/11/no-javascript-day/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Aggressively Defensive Programming]]></title>
		<link>https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/</link>
					<comments>https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 09 Apr 2013 11:15:22 +0000</pubDate>
				<category><![CDATA[mobile]]></category>
		<category><![CDATA[android]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[programming]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=7962</guid>

					<description><![CDATA[How much checking do we perform that our code is running as intended?  I found a curious bug this weekend, which made me think about some of the assumptions that we use when programming.  Imagine sorting an array using JavaScript.  var arr = [10, 5, 66, 8, 1, 3]; arr.sort();  So far, so normal.  Create an array of numbers, then sort that array.  The result should always be [1, 3, 5, 8, 10, 66]. …]]></description>
										<content:encoded><![CDATA[<p>How much checking do we perform that our code is running as intended?</p>

<p>I found a curious bug this weekend, which made me think about some of the assumptions that we use when programming.</p>

<p>Imagine sorting an array using JavaScript.</p>

<pre lang="javascript">var arr = [10, 5, 66, 8, 1, 3];
arr.sort();</pre>

<p>So far, so normal.  Create an array of numbers, then sort that array.  The result should always be [1, 3, 5, 8, 10, 66].</p>

<p>Would we ever need to do this?</p>

<pre lang="javascript">if (arr[0] &lt; arr[5]) {
   // Do something
} else {
   // THIS SHOULD NEVER HAPPEN!
   laws_of_physics_violated(arr);
}
</pre>

<p>Sorting an array in this way should <em>never</em> trigger an existential crisis.  But <strong>suppose it did</strong>.</p>

<h2 id="a-brief-word-on-android-fragmentation"><a href="https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/#a-brief-word-on-android-fragmentation">A Brief Word on Android Fragmentation</a></h2>

<p>Android fragmentation has never really bothered me.  It's usually a case of sensibly designing a UI to flow correctly and checking the capabilities of a device.  No more complex than designing for the web.</p>

<p>This weekend, my father and I spent the weekend building our first joint Android app - <a href="https://play.google.com/store/apps/details?id=mobi.shkspr.bridge.basic">First Bid Bridge</a> - it's a simple enough app that we decided to use a WebView and write the game logic in JavaScript.</p>

<p>All was going well until I tested the game on my Dad's phone.  It didn't work.  All the answers it produced were completely at odds with the answers on my phone.</p>

<p><a href="http://www.urbandictionary.com/define.php?term=Welcome%20to%20Facebook">Welcome To Facebook?</a></p>

<p>We tried this on every old phone I could find. Android 1.6, 2.3, 4.2 - they all worked. iPhone, Maemo, BlackBerry - hell, even my Internet TV worked flawlessly.</p>

<p>Painstakingly crawling through the logic, I found the problem.  A "sorted" array was reversed on his phone! (Galaxy Ace running 2.3.6 for those who want to know.)</p>

<h2 id="diagnosing"><a href="https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/#diagnosing">Diagnosing</a></h2>

<p>Our app looks at a hand of cards.  We split the hand into suits (Spade, Hearts, Diamonds, Clubs).  We record how many cards are in each suit.</p>

<p>We then sort the hand by length - if two of the suits have the same length, they are sorted in the order Spades &gt; Hearts &gt; Diamonds &gt; Clubs.</p>

<p>JavaScript allows us to define our own sorting function.  Here's ours (in simplified form)</p>

<p>First, we sort by length:
</p>

<pre lang="javascript">suitsArray.sort(function(x, y)
{
    var o1 = x["length"];
    var o2 = y["length"];
    return o2-o1;
});</pre>

<p>Then, we sort by suit order (if the lengths are the same):</p>

<pre lang="javascript">suitsArray.sort(function(x, y)
{
    var length1 = x["length"];
    var length2 = y["length"];

    if (length1 == length2)
    {
        var suit1 = x["suit"];
        var suit2 = y["suit"];

        if(suit1 == "spades")
        {return false;}

        if(suit1 == "hearts")
        {return false;}

        if(suit1 == "diamonds")
        {return false;}

        return true;
    }
});
</pre>

<p>In <em>every</em> device I tried, this sort worked perfectly.  Except on my dad's $@*&amp;ing phone.  ARGH!  What was even worse, is that we were an hour's journey away from each other, so I couldn't debug on the device.</p>

<p>So, that's where I came up with the idea of <strong>Aggressively Defensive Programming</strong>.  It may not be an original idea - but this is how it works.</p>

<h2 id="trust-but-verify"><a href="https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/#trust-but-verify">Trust, but verify</a></h2>

<p>"Доверяй, но проверяй" as the Russians would say.</p>

<p>Assume that your computer is crazy, possessed, or being zapped by cosmic radiation and check its compliance every step of the way.</p>

<p>Essentially, after every operation, you should verify that the operation has worked.  In this case, we did this (simplified):</p>

<pre lang="javascript">// The above sort

if ((arr[0]["length"] == arr[1]["length"]) &amp;&amp; (arr[1]["suit"] == "spades")) {
   // Dammit!
   // Sort the array manually
} else {
   // Carry on as normal
}</pre>

<h2 id="where-should-this-be-used"><a href="https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/#where-should-this-be-used">Where Should This Be Used</a></h2>

<p>There are four main scenarios where it makes sense to programs defensively.</p>

<ol>
    <li>Your code has a real impact on human health (aeroplane, nuclear reactor, medical device).</li>
    <li>The computer you're running on is in a harsh environment and therefore liable to unpredictable behaviour (Mars Rover)</li>
    <li>A multithreaded environment where your code cannot lock resources for itself.</li>
    <li>You are running on an untrusted computer i.e. one other than your own.</li>
</ol>

<p>For most of us, the first three are unlikely to trouble us.  But the final one is interesting.  Almost every piece of code we write will be run on a 3rd party's computer.  One over which we have very little control.</p>

<p>But that's what we do when we release apps - or run websites. The code we have lovingly crafted is being run on machines which may have all manner of quirks.</p>

<p>How aggressive should we be with our defences?  Do we assume that built in functions work - and only check ones we've written ourselves?</p>

<p>In <a href="http://www.amazon.co.uk/gp/product/1603861823/ref=as_li_ss_tl?ie=UTF8&amp;camp=1634&amp;creative=19450&amp;creativeASIN=1603861823&amp;linkCode=as2&amp;tag=shkspr-21">Principia Mathematica</a> Bertrand Russell famously provided proofs such as "1+1=2".
<a href="http://en.wikipedia.org/wiki/Principia_Mathematica"><img src="https://shkspr.mobi/blog/wp-content/uploads/2013/04/Principia_Mathematica_theorem_54-43.png" alt="Principia_Mathematica_theorem_54-43" width="640" height="267" class="alignnone size-full wp-image-7975"></a></p>

<p>Is that what we need? Mathematically verify every computer our code runs on?  That may be overkill.  But I'm starting to come round to the idea of verifying every operation - even if just to throw an exception rather than proceeding in an error prone state.</p>

<p>I leave you with an instructional extract from <a href="http://www.amazon.co.uk/gp/product/B003GK21AI/ref=as_li_ss_tl?ie=UTF8&amp;camp=1634&amp;creative=19450&amp;creativeASIN=B003GK21AI&amp;linkCode=as2&amp;tag=shkspr-21">Douglas Adam's Mostly Harmless</a>:</p>

<blockquote><p>On board the ship, everything was as it had been for millennia, deeply dark and Silent.

</p><p>Click, hum.

</p><p>At least, almost everything.

</p><p>Click, click, hum.

</p><p>Click, hum, click, hum, click, hum.

</p><p>Click, click, click, click, click, hum.

</p><p>Hmmm.

</p><p>A low level supervising program woke up a slightly higher level supervising program deep in the ship's semi-somnolent cyberbrain and reported to it that whenever it went click all it got was a hum.

</p><p>The higher level supervising program asked it what it was supposed to get, and the low level supervising program said that it couldn't remember exactly, but thought it was probably more of a sort of distant satisfied sigh, wasn't it? It didn't know what this hum was. Click, hum, click, hum. That was all it was getting.

</p><p>The higher level supervising program considered this and didn't like it. It asked the low level supervising program what exactly it was supervising and the low level supervising program said it couldn't remember that either, just that it was something that was meant to go click, sigh every ten years or so, which usually happened without fail. It had tried to consult its error look-up table but couldn't find it, which was why it had alerted the higher level supervising program to the problem .

</p><p>The higher level supervising program went to consult one of its own look-up tables to find out what the low level supervising program was meant to be supervising.

</p><p>It couldn't find the look-up table .

</p><p>Odd.

</p><p>It looked again. All it got was an error message. It tried to look up the error message in its error message look-up table and couldn't find that either. It allowed a couple of nanoseconds to go by while it went through all this again. Then it woke up its sector function supervisor.

</p><p>The sector function supervisor hit immediate problems. It called its supervising agent which hit problems too. Within a few millionths of a second virtual circuits that had lain dormant, some for years, some for centuries, were flaring into life throughout the ship. Something, somewhere, had gone terribly wrong, but none of the supervising programs could tell what it was. At every level, vital instructions were missing, and the instructions about what to do in the event of discovering that vital instructions were missing, were also missing.

</p><p>Small modules of software - agents - surged through the logical pathways, grouping, consulting, re-grouping. They quickly established that the ship's memory, all the way back to its central mission module, was in tatters. No amount of interrogation could determine what it was that had happened. Even the central mission module itself seemed to be damaged.

</p><p>This made the whole problem very simple to deal with. Replace the central mission module. There was another one, a backup, an exact duplicate of the original. It had to be physically replaced because, for safety reasons, there was no link whatsoever between the original and its backup. Once the central mission module was replaced it could itself supervise the reconstruction of the rest of the system in every detail, and all would be well.

</p><p>Robots were instructed to bring the backup central mission module from the shielded strong room, where they guarded it, to the ship's logic chamber for installation.

</p><p>This involved the lengthy exchange of emergency codes and protocols as the robots interrogated the agents as to the authenticity of the instructions. At last the robots were satisfied that all procedures were correct. They unpacked the backup central mission module from its storage housing, carried it out of the storage chamber, fell out of the ship and went spinning off into the void.

</p><p>This provided the first major clue as to what it was that was wrong.

</p><p>Further investigation quickly established what it was that had happened. A meteorite had knocked a large hole in the ship. The ship had not previously detected this because the meteorite had neatly knocked out that part of the ship's processing equipment which was supposed to detect if the ship had been hit by a meteorite.</p></blockquote>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=7962&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2013/04/aggressively-defensive-programming/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Visualising Conversation Threads In Hyperbolic Space]]></title>
		<link>https://shkspr.mobi/blog/2012/09/visualising-conversation-threads-in-hyperbolic-space/</link>
					<comments>https://shkspr.mobi/blog/2012/09/visualising-conversation-threads-in-hyperbolic-space/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 02 Sep 2012 09:12:54 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[app.net]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=6274</guid>

					<description><![CDATA[In 2009, Kosso and I petitioned Twitter to allow us to search for Tweets by their &#34;in reply to&#34; ID.  The idea was that developers could created a properly threaded view of conversations.  Of course, Twitter being ultra-responsive to developers, did absolutely nothing.  Skip three years into the future, and App.net is providing all the API goodness that Twitter doesn&#039;t.  This means that we can…]]></description>
										<content:encoded><![CDATA[<p>In 2009, <a href="http://code.google.com/p/twitter-api/issues/detail?id=1238">Kosso and I petitioned Twitter</a> to allow us to search for Tweets by their "in reply to" ID.</p>

<p>The idea was that developers could created a properly threaded view of conversations.</p>

<p>Of course, Twitter being ultra-responsive to developers, did <em>absolutely nothing</em>.</p>

<p>Skip three years into the future, and App.net is providing all the API goodness that Twitter doesn't.  This means that we can easily create new ways to view conversations.</p>

<p>So that is exactly what I've done.</p>

<p>You can <a href="https://web.archive.org/web/20121004005303/shkspr.mobi/hyper">play with HyperThread yourself at shkspr.mobi/hyper/</a>.</p>

<p>This is a <a href="http://en.wikipedia.org/wiki/Hyperbolic_tree">hypertree</a> visualisation of a simple conversation.  The centre node is the start of the conversation. Each reply goes off in its own thread.  Clicking on a node, re-centres the tree.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2012/09/Simple-Conversation-Thread.png" alt="Simple Conversation Thread" title="Simple Conversation Thread" width="530" height="536" class="aligncenter size-full wp-image-6282"></p>

<p>As a conversation grows in complexity, the conversation fades out at the edges. Clicking down a conversation thread allows you to easily follow a thread.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2012/09/Conversation-Thread.png" alt="Conversation Thread" title="Conversation Thread" width="530" height="536" class="aligncenter size-full wp-image-6281"></p>

<p>Of course, with extremely long and complex threads, the tree becomes more difficult to navigate. This is something I hope to fix in future versions.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2012/09/Complex-Conversation-Thread.png" alt="Complex Conversation Thread" title="Complex Conversation Thread" width="530" height="536" class="aligncenter size-full wp-image-6280"></p>

<p>Here is a video explaining how it all works.</p>

<iframe title="Threading Conversations Using A Hypertree" width="620" height="349" src="https://www.youtube.com/embed/mZOqw3UxpLg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>You can <a href="https://web.archive.org/web/20121004005303/shkspr.mobi/hyper">play with HyperThread yourself at </a><a href="https://shkspr.mobi/hyper/</a>.">https://shkspr.mobi/hyper/</a>.</p>

<p>The visualisation uses <a href="https://web.archive.org/web/20120906012229/http://thejit.org/">The JavaScript InfoVis Toolkit</a>.</p>

<p>The <a href="http://stackoverflow.com/questions/12219340/threaded-app-net-conversation-into-tree/">tree sorting algorithm is courtesy of the good folk at StackOverflow</a>.</p>

<p>A few points to make here:</p>

<ul>
    <li>This is a prototype. Some things may not work. Some essential functionality is missing.</li>
    <li>The layout algorithm is wonky. Sometimes the threaded layout looks really weird.</li>
    <li>The longer the conversation, the more complex and slower the visualisation.</li>
    <li>This only retrieves the first two hundred posts of any conversation.</li>
    <li>If posts have been deleted, the entire view may not work.</li>
    <li>Some threads just don't work.</li>
</ul>

<p>Inspired in part by <a href="https://web.archive.org/web/20200927052022/https://secure.flickr.com/photos/unkemptwomen/8080741422/in/photostream/">Lucy Pepper's Monkey.deck</a>
Lots more <a href="https://web.archive.org/web/20120908072005/http://adndev.net/?p=119">conversation about threading at adndev</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=6274&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2012/09/visualising-conversation-threads-in-hyperbolic-space/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Mobile Badvertising - BarCap]]></title>
		<link>https://shkspr.mobi/blog/2009/12/mobile-badvertising-barcap/</link>
					<comments>https://shkspr.mobi/blog/2009/12/mobile-badvertising-barcap/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 15 Dec 2009 12:15:47 +0000</pubDate>
				<category><![CDATA[badvertising]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[advert]]></category>
		<category><![CDATA[barclays]]></category>
		<category><![CDATA[javascript]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=1387</guid>

					<description><![CDATA[Ahhhh! The BBC.  Shining bastion of purity in a sea of commercial malaise.  Nothing can spoil its lustre.  Well, for those of us in the UK.  For the poor sods who find themselves living in the wilderness of ROW (Rest Of World) this is their BBC experience...  BBC With Adverts  The reasons for me seeing this are rather complex and involve VPNs and a co-located BES.  Regardless, what&#039;s this advert…]]></description>
										<content:encoded><![CDATA[<p>Ahhhh! The BBC.  Shining bastion of purity in a sea of commercial malaise.  Nothing can spoil its lustre.</p>

<p>Well, for those of us in the UK.  For the poor sods who find themselves living in the wilderness of ROW (Rest Of World) this is their BBC experience...</p>

<p></p><div id="attachment_1389" style="width: 490px" class="wp-caption aligncenter"><img aria-describedby="caption-attachment-1389" class="size-full wp-image-1389" title="BBC With Adverts" src="https://shkspr.mobi/blog/wp-content/uploads/2009/12/Capture8_17_35.jpg" alt="BBC With Adverts" width="480" height="320"><p id="caption-attachment-1389" class="wp-caption-text">BBC With Adverts</p></div><p></p>

<p>The reasons for me seeing this are rather complex and involve VPNs and a co-located BES.</p>

<p>Regardless, what's this advert like?  The text is small and indistinct.  The purpose of the message is rather vague. There's no allure.  Just a static banner.  How very 1996.  Well, gentle reader, as you know from <a href="https://shkspr.mobi/blog/tag/badvertising/">previous excursions</a>, I am prepared to click where no ordinary mortal would.</p>

<h2 id="click"><a href="https://shkspr.mobi/blog/2009/12/mobile-badvertising-barcap/#click">Click</a></h2>

<p></p><div id="attachment_1390" style="width: 490px" class="wp-caption aligncenter"><a href="http://www.BarCap.com/"><img aria-describedby="caption-attachment-1390" class="size-full wp-image-1390" title="www.BarCap.com" src="https://shkspr.mobi/blog/wp-content/uploads/2009/12/Capture8_20_6.jpg" alt="www.BarCap.com" width="480" height="320"></a><p id="caption-attachment-1390" class="wp-caption-text">www.BarCap.com</p></div><p></p>

<p>Oh - as the kids say - Em Gee.  What is this?</p>

<p>For those of you with poor eyesight, Barclays have pointed me straight at the desktop version of their website.  There is no mobile friendly site in view.  Not even a link to say "On a mobile? Click here."</p>

<p>To compound the error - the site is JavaScript heavy.&nbsp; Too heavy for the BlackBerry I use to view the site.&nbsp; You remember BlackBerry? The incredibly popular handset that every successful business-person carries.&nbsp; You know, the target demographic.</p>

<h2 id="what-to-do"><a href="https://shkspr.mobi/blog/2009/12/mobile-badvertising-barcap/#what-to-do">What To Do</a></h2>

<ul>
    <li>Make your advert interesting.</li>
    <li>Make your site mobile friendly - or at least suitable for your target demographic's handset</li>
</ul>

<p>Is it really that hard?</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=1387&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2009/12/mobile-badvertising-barcap/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
