<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jamesloper.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jamesloper.com/" rel="alternate" type="text/html" /><updated>2026-06-28T13:23:48+00:00</updated><id>https://jamesloper.com/feed.xml</id><title type="html">James Loper</title><subtitle>Personal site of James Loper</subtitle><entry><title type="html">Lemontron project</title><link href="https://jamesloper.com/lemontron" rel="alternate" type="text/html" title="Lemontron project" /><published>2024-08-19T00:00:00+00:00</published><updated>2024-08-19T00:00:00+00:00</updated><id>https://jamesloper.com/lemontron</id><content type="html" xml:base="https://jamesloper.com/lemontron"><![CDATA[<p><img src="/assets/lemontron.png" alt="port" class="banner" /></p>

<p>I have a zest for 3d printing, which is why I’ve created a new 3d Printer called the Lemontron.
<!--more--></p>

<p>I’ve been passionately working on this project with a single goal in mind: to create a 3D printer that is as versatile as it is portable. Lemontron is designed to fit neatly into a standard filament box, making it the perfect tool for anyone who values efficiency without compromising on quality.</p>

<p>Whether you’re a seasoned 3D printing enthusiast or just getting started, Lemontron offers something new. It’s easy to assemble, disassemble, and store, without sacrificing the performance you expect from a full-sized 3D printer. With Lemontron, you can take your zest for printing anywhere.</p>

<p>Check out the Lemontron at <a href="https://lemontron.com/">lemontron.com</a>.</p>]]></content><author><name></name></author><category term="post" /><summary type="html"><![CDATA[I have a zest for 3d printing, which is why I’ve created a new 3d Printer called the Lemontron.]]></summary></entry><entry><title type="html">Ubuntu Server Initial Setup</title><link href="https://jamesloper.com/ubuntu-server-initial-setup" rel="alternate" type="text/html" title="Ubuntu Server Initial Setup" /><published>2024-01-01T00:00:00+00:00</published><updated>2024-01-01T00:00:00+00:00</updated><id>https://jamesloper.com/ubuntu-server-initial-setup</id><content type="html" xml:base="https://jamesloper.com/ubuntu-server-initial-setup"><![CDATA[<p>Here is the song &amp; dance I do every time I start a new Ubuntu server. Nice cheat sheet for me to refer back to.
<!--more--></p>

<h2 id="install-tailscale">Install Tailscale</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://tailscale.com/install.sh | sh
</code></pre></div></div>

<h2 id="create-non-root-user--enable-ssh">Create non-root user &amp; enable SSH</h2>

<p>Create <code class="language-plaintext highlighter-rouge">ubuntu</code> user:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adduser ubuntu
usermod <span class="nt">-aG</span> <span class="nb">sudo </span>ubuntu
</code></pre></div></div>

<p>Most hosts allow keys to be uploaded at time of vps creation. Copy the keys to the new user.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync <span class="nt">--archive</span> <span class="nt">--chown</span><span class="o">=</span>ubuntu:ubuntu ~/.ssh /home/ubuntu
</code></pre></div></div>
<h2 id="login-as-ubuntu">Login as <code class="language-plaintext highlighter-rouge">ubuntu</code></h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh ubuntu@example
</code></pre></div></div>

<h2 id="quiet-the-login-message">Quiet the login message</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">touch</span> .hushlogin
</code></pre></div></div>

<h2 id="for-meteor-apps">For Meteor Apps</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-fsSL</span> https://deb.nodesource.com/setup_22.x | <span class="nb">sudo</span> <span class="nt">-E</span> bash -
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> nodejs jq build-essential
<span class="nb">sudo </span>npm <span class="nb">install</span> <span class="nt">-g</span> pm2
pm2 startup
</code></pre></div></div>

<h2 id="install-docker">Install docker</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Add Docker's official GPG key:</span>
<span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>ca-certificates curl
<span class="nb">sudo install</span> <span class="nt">-m</span> 0755 <span class="nt">-d</span> /etc/apt/keyrings
<span class="nb">sudo </span>curl <span class="nt">-fsSL</span> https://download.docker.com/linux/ubuntu/gpg <span class="nt">-o</span> /etc/apt/keyrings/docker.asc
<span class="nb">sudo chmod </span>a+r /etc/apt/keyrings/docker.asc

<span class="c"># Add the repository to Apt sources:</span>
<span class="nb">echo</span> <span class="se">\</span>
  <span class="s2">"deb [arch=</span><span class="si">$(</span>dpkg <span class="nt">--print-architecture</span><span class="si">)</span><span class="s2"> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu </span><span class="se">\</span><span class="s2">
  </span><span class="si">$(</span><span class="nb">.</span> /etc/os-release <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VERSION_CODENAME</span><span class="s2">"</span><span class="si">)</span><span class="s2"> stable"</span> | <span class="se">\</span>
  <span class="nb">sudo tee</span> /etc/apt/sources.list.d/docker.list <span class="o">&gt;</span> /dev/null
<span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt-get <span class="nb">install </span>docker-ce
<span class="nb">sudo </span>usermod <span class="nt">-aG</span> docker <span class="nv">$USER</span>
</code></pre></div></div>
<p>Log out and back in!</p>]]></content><author><name></name></author><category term="ubuntu" /><category term="server" /><summary type="html"><![CDATA[Here is the song &amp; dance I do every time I start a new Ubuntu server. Nice cheat sheet for me to refer back to.]]></summary></entry><entry><title type="html">Expose Docker Daemon on Windows</title><link href="https://jamesloper.com/docker-expose-daemon-windows" rel="alternate" type="text/html" title="Expose Docker Daemon on Windows" /><published>2023-12-31T00:00:00+00:00</published><updated>2023-12-31T00:00:00+00:00</updated><id>https://jamesloper.com/docker-expose-daemon-windows</id><content type="html" xml:base="https://jamesloper.com/docker-expose-daemon-windows"><![CDATA[<p>When you enable <strong>Expose daemon on tcp://localhost:2375 without TLS</strong> in Docker Desktop, you’d be forgiven for thinking that you could actually connect to the Docker Daemon from another machine in your network. But why?</p>

<!--more-->

<p>There are actually so many reasons. First, Docker’s 2375 is bound to localhost, so you might have looked into creating a proxyport with netsh, but read on. On Windows, Docker is actually running in a VM, so there’s a firewall there. There’s also a Windows firewall, so how can we connect to the Docker VM remotely?</p>

<h2 id="overcoming-the-complexities-of-network-traversal">Overcoming the complexities of network traversal</h2>

<p><a href="https://tailscale.com/">Tailscale</a> can be used to get around the issue while keeping everything secure. You are of course always free to install Tailscale directly on your computer. However, installing the Tailscale <em>Docker Extension</em> will allow you to connect to the underlying Docker VM. Crucially, Tailscale provides distinct IP addresses for these distinct machines. This allows you to easily overcome the complexities of network traversal, as you can simply use an IP for the Docker VM.</p>

<p>No need to mess with Firewall, netsh portproxy, or any of that nonsense.</p>

<h2 id="how-to-directly-connect-to-the-docker-machine-by-ip">How to directly connect to the Docker Machine by IP</h2>

<ol>
  <li>Install the Tailscale Docker extension</li>
  <li>Enable the setting “Expose daemon on tcp://localhost:2375 without TLS” in Docker Desktop</li>
  <li>Copy the IP address from the newly added Docker Machine (This is the Virtual Machine Docker is running under, not the Windows host)</li>
  <li>Add <code class="language-plaintext highlighter-rouge">"hosts": ["tcp://0.0.0.0:2375"]</code> to the Docker Machine configuration in Settings.</li>
  <li>Test the connection by running <code class="language-plaintext highlighter-rouge">docker -H tcp://[HOST_IP]:2375 ps</code></li>
</ol>

<p>We can plainly see how solid of a solution this is. Not only is it insanely simple and easy to use, you are now able to remotely access the docker daemon even from outside of your LAN. Incredible!</p>]]></content><author><name></name></author><category term="it" /><category term="networking" /><category term="vpn" /><summary type="html"><![CDATA[When you enable Expose daemon on tcp://localhost:2375 without TLS in Docker Desktop, you’d be forgiven for thinking that you could actually connect to the Docker Daemon from another machine in your network. But why?]]></summary></entry><entry><title type="html">Preserving Privacy (and Battery) in a Location-based App</title><link href="https://jamesloper.com/preserving-privacy-in-a-location-based-app" rel="alternate" type="text/html" title="Preserving Privacy (and Battery) in a Location-based App" /><published>2023-05-01T00:00:00+00:00</published><updated>2023-05-01T00:00:00+00:00</updated><id>https://jamesloper.com/preserving-privacy-in-a-location-based-app</id><content type="html" xml:base="https://jamesloper.com/preserving-privacy-in-a-location-based-app"><![CDATA[<p><img src="/assets/privacy.png" alt="Bad Ceo" class="banner" /></p>

<p>Life360, a company that claims its product is a “family safety service” has been selling
location data on its 31 million customers, including kids, since 2016.</p>

<!--more-->

<p>PathSense defends itself by citing the app “improved driver safety and saved numerous lives” but was it even technically
necessary to save all that data to the cloud, or could it have been possible to respect user privacy without losing its
major selling points?</p>

<p>That’s exactly the challenge I faced when I began work on a location-based app.</p>

<p>I have had the pleasure of working on a new dwell detection algorithm. What started as
a “scholarly” kernel-based approach, making attempts at polling updates at regular intervals, quickly revealed, through
testing, iOS would begin to throttle GPS updates to reduce the amount of time spent background processing. Working
within the limitations of iOS, we discovered how to play nice, and add some top shelf sophistication.</p>

<p>With the help of my crack team of pedestrian and vehicular co-founders, we created
an algorithm that performed…unreasonably well! We reached a hit rate above 90% by using the least amount of updates
possible. Counterintuitively, the GPS remains OFF when in motion, and turns on High
Accuracy during periods of stillness. Our battery usage is below 4% per day.</p>

<p>Basically, we draw a circle around you and move it with you, but when you walk or are still, we allow the circle to
trail behind, sticking to the ground rather than following you. If you ever escape a circle, it becomes a “leave”
event. After backtracking, we can determine the dwell’s starting event, being the first location update of the cluster
of updates existing inside the circle.</p>

<p>Many dwells are created from only two points, the activity recognition system triggering an initial GPS poll for the
starting point, and then a second poll when the user has moved a significant distance based off of low power draw
signals from other sensors that the OS uses. I think this is a metric of how efficient a dwell detection algorithm is
because it demonstrates how little the algorithm has to <em>fight</em> against the
data it receives.</p>

<p>I really enjoyed the process of utterly completely understanding everything involved in dwell detection. I specifically
loved how once we landed on the right algorithm, the code for dwell detection was reduced from 200 lines of useless
interpolation, averaging, heuristics and edge case handling, to only the essential 20 lines. Everything just fell
together all at once near the end. I think that satisfaction is a moment in my life I will always remember.</p>

<p>Also, during the entire time, it was never a question of if we needed to send the location updates to a server, that
part was nonsense and should have never come up.</p>]]></content><author><name></name></author><category term="gps" /><category term="location" /><category term="algorithm" /><category term="privacy" /><summary type="html"><![CDATA[Life360, a company that claims its product is a “family safety service” has been selling location data on its 31 million customers, including kids, since 2016.]]></summary></entry><entry><title type="html">How to tell if a port is in use in Ubuntu</title><link href="https://jamesloper.com/how-to-tell-if-a-port-is-in-use-ubuntu" rel="alternate" type="text/html" title="How to tell if a port is in use in Ubuntu" /><published>2022-01-06T00:00:00+00:00</published><updated>2022-01-06T00:00:00+00:00</updated><id>https://jamesloper.com/how-to-tell-if-a-port-is-in-use-ubuntu</id><content type="html" xml:base="https://jamesloper.com/how-to-tell-if-a-port-is-in-use-ubuntu"><![CDATA[<p><img src="/assets/port.png" alt="port" class="banner" /></p>

<p>Yet another impossible to remember thing!
<!--more--></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>lsof <span class="nt">-i</span>:22
</code></pre></div></div>]]></content><author><name></name></author><category term="guide" /><category term="ubuntu" /><category term="it" /><summary type="html"><![CDATA[Yet another impossible to remember thing!]]></summary></entry><entry><title type="html">Install Microsoft fonts in Ubuntu</title><link href="https://jamesloper.com/install-microsoft-fonts-ubuntu" rel="alternate" type="text/html" title="Install Microsoft fonts in Ubuntu" /><published>2021-12-22T00:00:00+00:00</published><updated>2021-12-22T00:00:00+00:00</updated><id>https://jamesloper.com/install-microsoft-fonts-ubuntu</id><content type="html" xml:base="https://jamesloper.com/install-microsoft-fonts-ubuntu"><![CDATA[<p><img src="/assets/fonts.png" alt="Microsoft Fonts" class="banner" /></p>

<p>Need to generate a PDF using the Arial font? Ubuntu doesn’t bundle proprietary software to avoid licensing issues, however you can easily install the Microsoft suite of fonts with one simple command.
<!--more--></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>ttf-mscorefonts-installer
<span class="nb">sudo </span>fc-cache <span class="nt">-f</span> <span class="nt">-v</span>
</code></pre></div></div>

<h2 id="thats-it">That’s it!</h2>

<p>By running this one simple command you will install all these fonts on Ubuntu! Most importantly you will install Arial, the most common Helvetica substitute.</p>

<ol>
  <li>Andale Mono</li>
  <li>Arial Black</li>
  <li>Arial (Bold, Italic, Bold Italic)</li>
  <li>Comic Sans MS (Bold)</li>
  <li>Courier New (Bold, Italic, Bold Italic)</li>
  <li>Georgia (Bold, Italic, Bold Italic)</li>
  <li>Impact</li>
  <li>Times New Roman (Bold, Italic, Bold Italic)</li>
  <li>Trebuchet (Bold, Italic, Bold Italic)</li>
  <li>Verdana (Bold, Italic, Bold Italic)</li>
  <li>Webdings</li>
</ol>]]></content><author><name></name></author><category term="guide" /><category term="ubuntu" /><category term="it" /><summary type="html"><![CDATA[Need to generate a PDF using the Arial font? Ubuntu doesn’t bundle proprietary software to avoid licensing issues, however you can easily install the Microsoft suite of fonts with one simple command.]]></summary></entry><entry><title type="html">Official meteor client in react native</title><link href="https://jamesloper.com/official-meteor-client-in-react-native" rel="alternate" type="text/html" title="Official meteor client in react native" /><published>2021-10-06T00:00:00+00:00</published><updated>2021-10-06T00:00:00+00:00</updated><id>https://jamesloper.com/official-meteor-client-in-react-native</id><content type="html" xml:base="https://jamesloper.com/official-meteor-client-in-react-native"><![CDATA[<p><img src="/assets/meteor-react-native.png" alt="meteor and react native" class="banner" /></p>

<p>When developing a React Native app with a meteor backend, you may be tempted to make use of the meteor client found on npm. However, that is not the best way!</p>

<!--more-->

<h2 id="how-not-to-implement-a-meteor-backend-in-react-native">How not to implement a Meteor backend in React Native</h2>

<p>When we search for a package that can integrate a Meteor backend into a React Native project, we are presented with several options, but no official package. These packages all seem to work at first, but once you dive deeper you discover differences from the official client, including bugs. Crucially, none of the packages follow Meteor DDP to spec, or differ from the official implementation. The way quiessence (one of meteor’s internal mechanisms) events fire, and out-of-order steps in the method call pipeline can cause bugs that are usually race conditions. 
In contrast, the meteor official code is frequently updated, inherently supports the latest features, and is battle tested and is in widespread use.</p>

<h2 id="the-solution">The solution</h2>

<p>When building a Meteor app for deployment, Meteor generates a js bundle. Using this knowledge, we can make use of the official, battle-tested client, and in doing so, make React Native just a little tiny bit less shitty.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Random</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">meteor/random</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">'</span><span class="s1">Test</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Random</span><span class="p">.</span><span class="nx">id</span><span class="p">(),</span> <span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">res</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<blockquote>
  <p>Importing meteor within a React Native project seems like madness!</p>
</blockquote>

<h2 id="table-of-contents">Table of contents</h2>

<ul>
  <li><a href="#setup-and-build-meteor-client">Setup and build the meteor client</a></li>
  <li><a href="#polyfills">Polyfills</a></li>
  <li><a href="#get-imports-to-work">Get imports to work</a></li>
  <li><a href="#usetracker">useTracker</a></li>
  <li><a href="#meteor-updates">Meteor updates</a></li>
</ul>

<h2 id="prior-work">Prior work</h2>

<p>This blog post is based off of the work of Urigo and his <a href="https://blog.meteor.com/leverage-the-power-of-meteor-with-any-client-side-framework-bfb909141008">meteor-client-bundler</a> and has been made more efficient by better polyfills and more readily modified scripts.</p>

<h2 id="quick-overview-of-the-folder-structure">Quick overview of the folder structure</h2>
<p>You can see below that there is a meteor-client folder, inside which is a folder for a stub of a meteor project, called app. The file create-bundle.js is the primary script which will run the meteor bundler and copy the output to a place within your react-native project directory. There is also post-bundle.js which is an asset used by create-bundle.js. Inside your utils folder in your project you will be copying in the polyfill.js file which will seamlessly translate web API’s to React Native API’s.</p>

<pre><code class="language-directory">📂 .meteor-client
   📁 app
   📄 create-bundle.js
   📄 post-bundle.js
📂 util
   📂 meteor
      📄 polyfill.js
      📄 meteor-client.js
</code></pre>

<h2 id="setup-and-build-meteor-client">Setup and build meteor client</h2>
<p><strong>Let’s get started!</strong> Create a folder to house the bundler and make a blank meteor app inside.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">mkdir</span> <span class="p">.</span><span class="nx">meteor</span><span class="o">-</span><span class="nx">client</span>
<span class="nx">cd</span> <span class="p">.</span><span class="nx">meteor</span><span class="o">-</span><span class="nx">client</span>
<span class="nx">meteor</span> <span class="nx">create</span> <span class="nx">app</span>
</code></pre></div></div>

<p>The bundler’s purpose is to build the meteor client to a temporary folder and copy the client to a specific directory in your project, in this case <code class="language-plaintext highlighter-rouge">utils/meteor/meteor-client.js</code>. It also installs some boilerplate code to the top and bottom of the bundle. Contents of <code class="language-plaintext highlighter-rouge">create-bundle.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">const</span> <span class="nx">execa</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">execa</span><span class="dl">'</span><span class="p">);</span>
 <span class="kd">const</span> <span class="nx">fs</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">);</span>
 <span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
 
 <span class="kd">const</span> <span class="nx">tempDir</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/tmp/meteor-client</span><span class="dl">'</span><span class="p">;</span>
 <span class="kd">const</span> <span class="nx">cwd</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">();</span>
 <span class="kd">const</span> <span class="nx">destinationFile</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">cwd</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utils/meteor/meteor-client.js</span><span class="dl">'</span><span class="p">);</span>
 
 <span class="nx">execa</span><span class="p">.</span><span class="nx">sync</span><span class="p">(</span><span class="dl">'</span><span class="s1">meteor</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">'</span><span class="s1">build</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">--debug</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">--directory</span><span class="dl">'</span><span class="p">,</span> <span class="nx">tempDir</span><span class="p">],</span> <span class="p">{</span><span class="na">cwd</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">cwd</span><span class="p">,</span> <span class="dl">'</span><span class="s1">.meteor-client/app</span><span class="dl">'</span><span class="p">),</span> <span class="na">stdio</span><span class="p">:</span> <span class="dl">'</span><span class="s1">inherit</span><span class="dl">'</span><span class="p">});</span>
 
 <span class="c1">// Create top of file</span>
 <span class="kd">const</span> <span class="nx">runtimeConfig</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">__meteor_runtime_config__ = Object.assign({meteorEnv: {}}, window.__meteor_runtime_config__);</span><span class="se">\n\n</span><span class="dl">'</span><span class="p">;</span>
 <span class="nx">fs</span><span class="p">.</span><span class="nx">unlinkSync</span><span class="p">(</span><span class="nx">destinationFile</span><span class="p">);</span>
 <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">destinationFile</span><span class="p">,</span> <span class="nx">runtimeConfig</span><span class="p">);</span>
 
 <span class="c1">// Copy modules to file</span>
 <span class="kd">const</span> <span class="nx">buildPath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">tempDir</span><span class="p">,</span> <span class="dl">'</span><span class="s1">bundle/programs/web.browser</span><span class="dl">'</span><span class="p">);</span>
 <span class="kd">const</span> <span class="nx">program</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">buildPath</span><span class="p">,</span> <span class="dl">'</span><span class="s1">program.json</span><span class="dl">'</span><span class="p">));</span>
 <span class="nx">program</span><span class="p">.</span><span class="nx">manifest</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">pack</span> <span class="o">=&gt;</span> <span class="p">{</span>
     <span class="k">return</span> <span class="p">(</span><span class="nx">pack</span><span class="p">.</span><span class="nx">where</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">client</span><span class="dl">'</span> <span class="o">&amp;&amp;</span>
         <span class="nx">pack</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">js</span><span class="dl">'</span> <span class="o">&amp;&amp;</span>
         <span class="nx">path</span><span class="p">.</span><span class="nx">dirname</span><span class="p">(</span><span class="nx">pack</span><span class="p">.</span><span class="nx">path</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">packages</span><span class="dl">'</span><span class="p">)</span> <span class="o">||</span>
         <span class="nx">pack</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="dl">'</span><span class="s1">global-imports</span><span class="dl">'</span><span class="p">)</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
 <span class="p">}).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">pack</span> <span class="o">=&gt;</span> <span class="p">{</span>
     <span class="kd">const</span> <span class="nx">packFile</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">buildPath</span><span class="p">,</span> <span class="nx">pack</span><span class="p">.</span><span class="nx">path</span><span class="p">);</span>
     <span class="kd">const</span> <span class="nx">packContent</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">packFile</span><span class="p">).</span><span class="nx">toString</span><span class="p">()</span> <span class="o">+</span> <span class="dl">'</span><span class="se">\n\n</span><span class="dl">'</span><span class="p">;</span>
     <span class="nx">fs</span><span class="p">.</span><span class="nx">appendFileSync</span><span class="p">(</span><span class="nx">destinationFile</span><span class="p">,</span> <span class="nx">packContent</span><span class="p">);</span>
 <span class="p">});</span>
 
 <span class="c1">// Append bottom of file</span>
 <span class="kd">const</span> <span class="nx">postBundleFile</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">post-bundle.js</span><span class="dl">'</span><span class="p">);</span>
 <span class="kd">const</span> <span class="nx">postBundleContent</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">postBundleFile</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span>
 <span class="nx">fs</span><span class="p">.</span><span class="nx">appendFileSync</span><span class="p">(</span><span class="nx">destinationFile</span><span class="p">,</span> <span class="nx">postBundleContent</span><span class="p">);</span>
</code></pre></div></div>

<p>Contents of <code class="language-plaintext highlighter-rouge">post-bundle.js</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Disable hot-code-push</span>
 <span class="k">if</span> <span class="p">(</span><span class="nx">Package</span><span class="p">.</span><span class="nx">reload</span><span class="p">)</span> <span class="p">{</span>
     <span class="nx">Package</span><span class="p">.</span><span class="nx">reload</span><span class="p">.</span><span class="nx">Reload</span><span class="p">.</span><span class="nx">_onMigrate</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
         <span class="k">return</span> <span class="p">[</span><span class="kc">false</span><span class="p">];</span>
     <span class="p">});</span>
 <span class="p">}</span>
</code></pre></div></div>

<p>Install the devDependencies:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> <span class="nt">--dev</span> execa
</code></pre></div></div>

<p>In your <code class="language-plaintext highlighter-rouge">package.json</code> add a new script:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="p">...</span>
	<span class="dl">"</span><span class="s2">meteor-client-bundle</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">node .meteor-client/create-bundle.js</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And run it…</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">npm</span> <span class="nx">run</span> <span class="nx">meteor</span><span class="o">-</span><span class="nx">client</span><span class="o">-</span><span class="nx">bundle</span>
</code></pre></div></div>

<p>In your root component, import the polyfill and the meteor client:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="dl">'</span><span class="s1">./utils/meteor/polyfills</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">'</span><span class="s1">./utils/meteor/meteor-client</span><span class="dl">'</span><span class="p">;</span>

<span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">registerRootComponent</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">expo</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">App</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./containers/App</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">registerRootComponent</span><span class="p">(</span><span class="nx">App</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="polyfills">Polyfills</h2>

<p>This generated bundle is not much use yet, it was intended to be run within a browser environment. So we must polyfill all those browser apis. This polyfill depends on react-native-mmkv-storage.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install </span>react-native-mmkv-storage
</code></pre></div></div>

<p>Contents of <code class="language-plaintext highlighter-rouge">/util/meteor/polyfill.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Good stuff that is required to run meteor client</span>
<span class="c1">// https://blog.meteor.com/leverage-the-power-of-meteor-with-any-client-side-framework-bfb909141008</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">Dimensions</span><span class="p">,</span> <span class="nx">Linking</span><span class="p">,</span> <span class="nx">Vibration</span><span class="p">,</span> <span class="nx">Alert</span><span class="p">,</span> <span class="nx">AppState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-native</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">NetInfo</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@react-native-community/netinfo</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">MMKVStorage</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-native-mmkv-storage</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">baseUrl</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../../constants/baseUrl</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">polyfillMmkv</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MMKVStorage</span><span class="p">.</span><span class="nx">Loader</span><span class="p">().</span><span class="nx">withInstanceID</span><span class="p">(</span><span class="dl">'</span><span class="s1">localStorage</span><span class="dl">'</span><span class="p">).</span><span class="nx">initialize</span><span class="p">();</span>

<span class="nb">global</span><span class="p">.</span><span class="nx">__meteor_runtime_config__</span> <span class="o">=</span> <span class="p">{</span><span class="dl">'</span><span class="s1">DDP_DEFAULT_CONNECTION_URL</span><span class="dl">'</span><span class="p">:</span> <span class="nx">baseUrl</span><span class="p">};</span>

<span class="kd">let</span> <span class="nx">currentUrl</span> <span class="o">=</span> <span class="dl">''</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">readyState</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">complete</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">netInfo</span> <span class="o">=</span> <span class="p">{</span>
	<span class="na">changeListeners</span><span class="p">:</span> <span class="p">[],</span>
	<span class="nx">addEventListener</span><span class="p">(</span><span class="nx">eventName</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">eventName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">change</span><span class="dl">'</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nx">changeListeners</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
	<span class="p">},</span>
	<span class="nx">removeEventListener</span><span class="p">(</span><span class="nx">eventName</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">eventName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">change</span><span class="dl">'</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nx">changeListeners</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">changeListeners</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">callback</span><span class="p">),</span> <span class="mi">1</span><span class="p">);</span>
	<span class="p">},</span>
<span class="p">};</span>

<span class="c1">// Forward react native NetInfo events to polyfill</span>
<span class="nx">NetInfo</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nx">info</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="c1">// console.log('update', info);</span>
	<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">netInfo</span><span class="p">,</span> <span class="nx">info</span><span class="p">);</span>
	<span class="nx">netInfo</span><span class="p">.</span><span class="nx">changeListeners</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">callback</span> <span class="o">=&gt;</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">info</span><span class="p">));</span>
<span class="p">});</span>

<span class="c1">// Sync currentUrl with new url</span>
<span class="nx">Linking</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">url</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">)</span> <span class="nx">currentUrl</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span>
<span class="p">});</span>

<span class="kd">function</span> <span class="nx">parseUrl</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">const</span> <span class="nx">urlArr</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">#</span><span class="dl">'</span><span class="p">);</span>
	<span class="k">return</span> <span class="p">{</span>
		<span class="kd">get</span> <span class="nx">href</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">urlArr</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
		<span class="p">},</span>
		<span class="kd">set</span> <span class="nx">href</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Linking</span><span class="p">.</span><span class="nx">openURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
		<span class="p">},</span>
		<span class="kd">get</span> <span class="nx">pathname</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">href</span><span class="p">;</span>
		<span class="p">},</span>
		<span class="kd">get</span> <span class="nx">hash</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">urlArr</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">?</span> <span class="s2">`#</span><span class="p">${</span><span class="nx">urlArr</span><span class="p">[</span><span class="mi">1</span><span class="p">]}</span><span class="s2">`</span> <span class="p">:</span> <span class="dl">""</span><span class="p">;</span>
		<span class="p">},</span>
		<span class="kd">set</span> <span class="nx">hash</span><span class="p">(</span><span class="nx">hash</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Linking</span><span class="p">.</span><span class="nx">openURL</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">href</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">#</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">hash</span><span class="p">);</span>
		<span class="p">},</span>
		<span class="nx">replace</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Linking</span><span class="p">.</span><span class="nx">openURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
		<span class="p">},</span>
		<span class="nx">toString</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">url</span><span class="p">;</span>
		<span class="p">},</span>
	<span class="p">};</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">localStorage</span> <span class="o">=</span> <span class="p">{</span>
	<span class="na">setItem</span><span class="p">:</span> <span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">val</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="nx">polyfillMmkv</span><span class="p">.</span><span class="nx">setString</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nb">String</span><span class="p">(</span><span class="nx">val</span><span class="p">));</span>
	<span class="p">},</span>
	<span class="na">getItem</span><span class="p">:</span> <span class="nx">id</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">polyfillMmkv</span><span class="p">.</span><span class="nx">getString</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
	<span class="p">},</span>
	<span class="na">removeItem</span><span class="p">:</span> <span class="nx">id</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="nx">polyfillMmkv</span><span class="p">.</span><span class="nx">removeItem</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
	<span class="p">},</span>
	<span class="na">clear</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="nx">polyfillMmkv</span><span class="p">.</span><span class="nx">clearStore</span><span class="p">();</span>
	<span class="p">},</span>
<span class="p">};</span>

<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperties</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">localStorage</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">get</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">localStorage</span><span class="p">,</span>
	<span class="p">},</span>
	<span class="na">sessionStorage</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">get</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">localStorage</span><span class="p">,</span>
	<span class="p">},</span>
	<span class="na">document</span><span class="p">:</span> <span class="p">{</span>
		<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="p">{</span>
				<span class="nx">readyState</span><span class="p">,</span>
				<span class="nx">addEventListener</span><span class="p">(</span><span class="nx">eventName</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
					<span class="k">if</span> <span class="p">(</span><span class="nx">eventName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">deviceready</span><span class="dl">'</span> <span class="o">||</span> <span class="nx">eventName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
						<span class="nx">callback</span><span class="p">();</span>
					<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">eventName</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">visibilitychange</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
						<span class="nx">AppState</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">change</span><span class="dl">'</span><span class="p">,</span> <span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
							<span class="nx">callback</span><span class="p">({</span><span class="dl">'</span><span class="s1">visibilitychange</span><span class="dl">'</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/inactive|background/</span><span class="p">)</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">visible</span><span class="dl">'</span><span class="p">});</span>
						<span class="p">});</span>
					<span class="p">}</span>
				<span class="p">},</span>
				<span class="nx">getElementsByTagName</span><span class="p">()</span> <span class="p">{</span>
					<span class="k">return</span> <span class="p">{</span>
						<span class="nx">item</span><span class="p">()</span> <span class="p">{</span>
							<span class="k">return</span> <span class="p">{</span>
								<span class="nx">appendChild</span><span class="p">()</span> <span class="p">{</span>
								<span class="p">},</span>
							<span class="p">};</span>
						<span class="p">},</span>
					<span class="p">};</span>
				<span class="p">},</span>
				<span class="nx">createElement</span><span class="p">()</span> <span class="p">{</span>
					<span class="k">return</span> <span class="p">{</span>
						<span class="nx">setAttribute</span><span class="p">()</span> <span class="p">{</span>
						<span class="p">},</span>
						<span class="kd">get</span> <span class="nx">pathname</span><span class="p">()</span> <span class="p">{</span>
							<span class="k">return</span> <span class="dl">''</span><span class="p">;</span>
						<span class="p">},</span>
					<span class="p">};</span>
				<span class="p">},</span>
				<span class="kd">get</span> <span class="nx">hidden</span><span class="p">()</span> <span class="p">{</span>
					<span class="k">return</span> <span class="nx">AppState</span><span class="p">.</span><span class="nx">currentState</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/inactive|background/</span><span class="p">);</span>
				<span class="p">},</span>
				<span class="kd">get</span> <span class="nx">visibilityState</span><span class="p">()</span> <span class="p">{</span>
					<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">hidden</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">visible</span><span class="dl">'</span><span class="p">;</span>
				<span class="p">},</span>
			<span class="p">};</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">self</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
		<span class="na">value</span><span class="p">:</span> <span class="nb">global</span><span class="p">,</span>
	<span class="p">},</span>
	<span class="na">addEventListener</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
		<span class="nx">value</span><span class="p">(</span><span class="nx">eventName</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">switch</span> <span class="p">(</span><span class="nx">eventName</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">case</span> <span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">:</span>
					<span class="k">break</span><span class="p">;</span>
				<span class="k">case</span> <span class="dl">'</span><span class="s1">online</span><span class="dl">'</span><span class="p">:</span>
					<span class="nx">netInfo</span><span class="p">.</span><span class="nx">changeListeners</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
					<span class="k">break</span><span class="p">;</span>
				<span class="k">case</span> <span class="dl">'</span><span class="s1">hashchange</span><span class="dl">'</span><span class="p">:</span>
					<span class="kd">let</span> <span class="nx">oldHash</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">;</span>
					<span class="nx">Linking</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">url</span><span class="dl">'</span><span class="p">,</span> <span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
						<span class="kd">const</span> <span class="p">{</span><span class="nx">hash</span><span class="p">}</span> <span class="o">=</span> <span class="nx">parseUrl</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
						<span class="k">if</span> <span class="p">(</span><span class="nx">hash</span> <span class="o">!==</span> <span class="nx">oldHash</span><span class="p">)</span> <span class="nx">callback</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
						<span class="nx">oldHash</span> <span class="o">=</span> <span class="nx">hash</span><span class="p">;</span>
					<span class="p">});</span>
					<span class="k">break</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">attachEvent</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
		<span class="nx">value</span><span class="p">(</span><span class="nx">eventNameWithOn</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
			<span class="kd">const</span> <span class="nx">eventName</span> <span class="o">=</span> <span class="nx">eventNameWithOn</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="dl">'</span><span class="s1">on</span><span class="dl">'</span><span class="p">,</span> <span class="dl">''</span><span class="p">);</span>
			<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="nx">eventName</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">onload</span><span class="p">:</span> <span class="p">{</span>
		<span class="kd">set</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">onhashchange</span><span class="p">:</span> <span class="p">{</span>
		<span class="kd">set</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">hashchange</span><span class="dl">'</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">outerWidth</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
		<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Dimensions</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">window</span><span class="dl">'</span><span class="p">).</span><span class="nx">width</span><span class="p">;</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">outerHeight</span><span class="p">:</span> <span class="p">{</span>
		<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
		<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Dimensions</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">window</span><span class="dl">'</span><span class="p">).</span><span class="nx">height</span><span class="p">;</span>
		<span class="p">},</span>
	<span class="p">},</span>
	<span class="na">alert</span><span class="p">:</span> <span class="p">{</span>
		<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
			<span class="k">return</span> <span class="nx">Alert</span><span class="p">.</span><span class="nx">alert</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">Alert</span><span class="p">);</span>
		<span class="p">},</span>
	<span class="p">},</span>
<span class="p">});</span>

<span class="kd">const</span> <span class="nx">geolocation</span> <span class="o">=</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">geolocation</span><span class="p">;</span>

<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="dl">'</span><span class="s1">navigator</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
	<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
		<span class="k">return</span> <span class="p">{</span>
			<span class="kd">get</span> <span class="nx">onLine</span><span class="p">()</span> <span class="p">{</span>
				<span class="k">return</span> <span class="o">!!</span><span class="nx">netInfo</span><span class="p">.</span><span class="nx">type</span><span class="p">;</span>
			<span class="p">},</span>
			<span class="kd">get</span> <span class="nx">vibrate</span><span class="p">()</span> <span class="p">{</span>
				<span class="k">return</span> <span class="nx">Vibration</span><span class="p">.</span><span class="nx">vibrate</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">Vibration</span><span class="p">);</span>
			<span class="p">},</span>
			<span class="kd">get</span> <span class="nx">geolocation</span><span class="p">()</span> <span class="p">{</span>
				<span class="k">return</span> <span class="nx">geolocation</span><span class="p">;</span>
			<span class="p">},</span>
			<span class="kd">get</span> <span class="nx">connection</span><span class="p">()</span> <span class="p">{</span>
				<span class="k">return</span> <span class="nx">netInfo</span><span class="p">;</span>
			<span class="p">},</span>
		<span class="p">};</span>
	<span class="p">},</span>
<span class="p">});</span>

<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="dl">'</span><span class="s1">location</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
	<span class="kd">get</span><span class="p">()</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">parseUrl</span><span class="p">(</span><span class="nx">currentUrl</span><span class="p">);</span>
	<span class="p">},</span>
	<span class="kd">set</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">Linking</span><span class="p">.</span><span class="nx">openURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
	<span class="p">},</span>
<span class="p">});</span>


<span class="kd">let</span> <span class="nx">newWindowMock</span> <span class="o">=</span> <span class="p">{</span><span class="na">closed</span><span class="p">:</span> <span class="kc">false</span><span class="p">};</span>

<span class="nb">Object</span><span class="p">.</span><span class="nx">defineProperty</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="dl">'</span><span class="s1">open</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
	<span class="na">configurable</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
	<span class="na">value</span><span class="p">:</span> <span class="nx">url</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="nx">newWindowMock</span><span class="p">.</span><span class="nx">closed</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
		<span class="nx">Linking</span><span class="p">.</span><span class="nx">openURL</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
		<span class="k">return</span> <span class="nx">newWindowMock</span><span class="p">;</span>
	<span class="p">},</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="get-imports-to-work">Get imports to work</h2>

<p>At this point you can access the meteor packages but unless you are a maniac, this is really unsatisfying.</p>

<table>
  <thead>
    <tr>
      <th>What works as of now…</th>
      <th>How we would rather have it work…</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">const {check} = Package['check'];</code></td>
      <td><code class="language-plaintext highlighter-rouge">import { check } from 'meteor/check';</code></td>
    </tr>
  </tbody>
</table>

<p>To be able to <code class="language-plaintext highlighter-rouge">import</code> meteor packages, you can implement an adapter in babel. Using AST explorer I developed a “<a href="https://astexplorer.net/#/gist/359ce39f3069f400426a2e1f3e0bce41/bedeac44b0f54d13945bb289e0d45126d87a3d49">import from meteor package</a>” transpiler. By including the transpiler in <code class="language-plaintext highlighter-rouge">babel.config.js</code> babel (which is built in) will immediately make the new syntax work.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">adaptMeteor</span> <span class="o">=</span> <span class="p">(</span><span class="nx">babel</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="kd">const</span> <span class="p">{</span><span class="na">types</span><span class="p">:</span> <span class="nx">t</span><span class="p">}</span> <span class="o">=</span> <span class="nx">babel</span><span class="p">;</span>
	<span class="k">return</span> <span class="p">{</span>
		<span class="na">visitor</span><span class="p">:</span> <span class="p">{</span>
			<span class="nx">ImportDeclaration</span><span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
				<span class="kd">const</span> <span class="nx">modulePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">source</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
				<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">modulePath</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">meteor/</span><span class="dl">'</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>
				<span class="kd">const</span> <span class="nx">moduleName</span> <span class="o">=</span> <span class="nx">modulePath</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">7</span><span class="p">);</span>
				<span class="kd">const</span> <span class="nx">newNode</span> <span class="o">=</span> <span class="nx">t</span><span class="p">.</span><span class="nx">variableDeclaration</span><span class="p">(</span><span class="dl">'</span><span class="s1">const</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span>
					<span class="nx">t</span><span class="p">.</span><span class="nx">variableDeclarator</span><span class="p">(</span>
						<span class="nx">t</span><span class="p">.</span><span class="nx">objectPattern</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">specifiers</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="p">{</span>
							<span class="k">return</span> <span class="nx">t</span><span class="p">.</span><span class="nx">objectProperty</span><span class="p">(</span><span class="nx">t</span><span class="p">.</span><span class="nx">identifier</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">imported</span><span class="p">.</span><span class="nx">name</span><span class="p">),</span> <span class="nx">t</span><span class="p">.</span><span class="nx">identifier</span><span class="p">(</span><span class="nx">r</span><span class="p">.</span><span class="nx">local</span><span class="p">.</span><span class="nx">name</span><span class="p">),</span> <span class="kc">false</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
						<span class="p">})),</span>
						<span class="nx">t</span><span class="p">.</span><span class="nx">identifier</span><span class="p">(</span><span class="s2">`Package['</span><span class="p">${</span><span class="nx">moduleName</span><span class="p">}</span><span class="s2">']`</span><span class="p">),</span>
					<span class="p">),</span>
				<span class="p">]);</span>
				<span class="nx">path</span><span class="p">.</span><span class="nx">replaceWith</span><span class="p">(</span><span class="nx">newNode</span><span class="p">);</span>
			<span class="p">},</span>
		<span class="p">},</span>
	<span class="p">};</span>
<span class="p">};</span>

<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">api</span><span class="p">)</span> <span class="p">{</span>
	<span class="nx">api</span><span class="p">.</span><span class="nx">cache</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
	<span class="k">return</span> <span class="p">{</span>
		<span class="na">presets</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">babel-preset-expo</span><span class="dl">'</span><span class="p">],</span>
		<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nx">adaptMeteor</span><span class="p">],</span>
	<span class="p">};</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="usetracker">useTracker</h2>

<p>Here’s a useTracker function you can drop into your project!</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">Meteor</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">meteor/meteor</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Tracker</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">meteor/tracker</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useReducer</span><span class="p">,</span> <span class="nx">useEffect</span><span class="p">,</span> <span class="nx">useRef</span><span class="p">,</span> <span class="nx">useMemo</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>

<span class="c1">// Used to create a forceUpdate from useReducer. Forces update by</span>
<span class="c1">// incrementing a number whenever the dispatch method is invoked.</span>
<span class="kd">const</span> <span class="nx">fur</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">useForceUpdate</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">useReducer</span><span class="p">(</span><span class="nx">fur</span><span class="p">,</span> <span class="mi">0</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">useTrackerNoDeps</span> <span class="o">=</span> <span class="p">(</span><span class="nx">reactiveFn</span><span class="p">,</span> <span class="nx">skipUpdate</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="kd">const</span> <span class="p">{</span><span class="na">current</span><span class="p">:</span> <span class="nx">refs</span><span class="p">}</span> <span class="o">=</span> <span class="nx">useRef</span><span class="p">({</span>
		<span class="na">isMounted</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
		<span class="na">trackerData</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
	<span class="p">});</span>
	<span class="kd">const</span> <span class="nx">forceUpdate</span> <span class="o">=</span> <span class="nx">useForceUpdate</span><span class="p">();</span>
	<span class="c1">// Without deps, always dispose and recreate the computation with every render.</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
		<span class="c1">// @ts-ignore This makes TS think ref.computation is "never" set</span>
		<span class="k">delete</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="c1">// Use Tracker.nonreactive in case we are inside a Tracker Computation.</span>
	<span class="c1">// This can happen if someone calls `ReactDOM.render` inside a Computation.</span>
	<span class="c1">// In that case, we want to opt out of the normal behavior of nested</span>
	<span class="c1">// Computations, where if the outer one is invalidated or stopped,</span>
	<span class="c1">// it stops the inner one.</span>
	<span class="nx">Tracker</span><span class="p">.</span><span class="nx">nonreactive</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">autorun</span><span class="p">((</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span> <span class="o">=</span> <span class="nx">c</span><span class="p">;</span>
		<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">reactiveFn</span><span class="p">(</span><span class="nx">c</span><span class="p">);</span>
		<span class="k">if</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">firstRun</span><span class="p">)</span> <span class="p">{</span>
			<span class="c1">// Always run the reactiveFn on firstRun</span>
			<span class="nx">refs</span><span class="p">.</span><span class="nx">trackerData</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
		<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">skipUpdate</span> <span class="o">||</span> <span class="o">!</span><span class="nx">skipUpdate</span><span class="p">(</span><span class="nx">refs</span><span class="p">.</span><span class="nx">trackerData</span><span class="p">,</span> <span class="nx">data</span><span class="p">))</span> <span class="p">{</span>
			<span class="c1">// For any reactive change, forceUpdate and let the next render rebuild the computation.</span>
			<span class="nx">forceUpdate</span><span class="p">();</span>
		<span class="p">}</span>
	<span class="p">}));</span>
	<span class="c1">// To clean up side effects in render, stop the computation immediately</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">Meteor</span><span class="p">.</span><span class="nx">defer</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">&amp;&amp;</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">)</span> <span class="p">{</span>
				<span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
				<span class="k">delete</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">});</span>
	<span class="p">}</span>
	<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="c1">// Let subsequent renders know we are mounted (render is committed).</span>
		<span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
		<span class="c1">// In some cases, the useEffect hook will run before Meteor.defer, such as</span>
		<span class="c1">// when React.lazy is used. In those cases, we might as well leave the</span>
		<span class="c1">// computation alone!</span>
		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">)</span> <span class="p">{</span>
			<span class="c1">// Render is committed, but we no longer have a computation. Invoke</span>
			<span class="c1">// forceUpdate and let the next render recreate the computation.</span>
			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">skipUpdate</span><span class="p">)</span> <span class="p">{</span>
				<span class="nx">forceUpdate</span><span class="p">();</span>
			<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
				<span class="nx">Tracker</span><span class="p">.</span><span class="nx">nonreactive</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">autorun</span><span class="p">((</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
					<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">reactiveFn</span><span class="p">(</span><span class="nx">c</span><span class="p">);</span>
					<span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span> <span class="o">=</span> <span class="nx">c</span><span class="p">;</span>
					<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">skipUpdate</span><span class="p">(</span><span class="nx">refs</span><span class="p">.</span><span class="nx">trackerData</span><span class="p">,</span> <span class="nx">data</span><span class="p">))</span> <span class="p">{</span>
						<span class="c1">// For any reactive change, forceUpdate and let the next render rebuild the computation.</span>
						<span class="nx">forceUpdate</span><span class="p">();</span>
					<span class="p">}</span>
				<span class="p">}));</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="c1">// stop the computation on unmount</span>
		<span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
			<span class="kd">var</span> <span class="nx">_a</span><span class="p">;</span>
			<span class="p">(</span><span class="nx">_a</span> <span class="o">=</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">)</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">_a</span> <span class="o">===</span> <span class="k">void</span> <span class="mi">0</span> <span class="p">?</span> <span class="k">void</span> <span class="mi">0</span> <span class="p">:</span> <span class="nx">_a</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
			<span class="k">delete</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">computation</span><span class="p">;</span>
			<span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
		<span class="p">};</span>
	<span class="p">},</span> <span class="p">[]);</span>
	<span class="k">return</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">trackerData</span><span class="p">;</span>
<span class="p">};</span>
<span class="kd">const</span> <span class="nx">useTrackerWithDeps</span> <span class="o">=</span> <span class="p">(</span><span class="nx">reactiveFn</span><span class="p">,</span> <span class="nx">deps</span><span class="p">,</span> <span class="nx">skipUpdate</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
	<span class="kd">const</span> <span class="nx">forceUpdate</span> <span class="o">=</span> <span class="nx">useForceUpdate</span><span class="p">();</span>
	<span class="kd">const</span> <span class="p">{</span><span class="na">current</span><span class="p">:</span> <span class="nx">refs</span><span class="p">}</span> <span class="o">=</span> <span class="nx">useRef</span><span class="p">({</span><span class="nx">reactiveFn</span><span class="p">});</span>
	<span class="c1">// keep reactiveFn ref fresh</span>
	<span class="nx">refs</span><span class="p">.</span><span class="nx">reactiveFn</span> <span class="o">=</span> <span class="nx">reactiveFn</span><span class="p">;</span>
	<span class="nx">useMemo</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="c1">// To jive with the lifecycle interplay between Tracker/Subscribe, run the</span>
		<span class="c1">// reactive function in a computation, then stop it, to force flush cycle.</span>
		<span class="kd">const</span> <span class="nx">comp</span> <span class="o">=</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">nonreactive</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">autorun</span><span class="p">((</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
			<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">reactiveFn</span><span class="p">();</span>
			<span class="k">if</span> <span class="p">(</span><span class="nx">c</span><span class="p">.</span><span class="nx">firstRun</span><span class="p">)</span> <span class="p">{</span>
				<span class="nx">refs</span><span class="p">.</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
			<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">skipUpdate</span> <span class="o">||</span> <span class="o">!</span><span class="nx">skipUpdate</span><span class="p">(</span><span class="nx">refs</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span> <span class="nx">data</span><span class="p">))</span> <span class="p">{</span>
				<span class="nx">refs</span><span class="p">.</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
				<span class="nx">forceUpdate</span><span class="p">();</span>
			<span class="p">}</span>
		<span class="p">}));</span>
		<span class="c1">// In some cases, the useEffect hook will run before Meteor.defer, such as</span>
		<span class="c1">// when React.lazy is used. This will allow it to be stopped earlier in</span>
		<span class="c1">// useEffect if needed.</span>
		<span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span> <span class="o">=</span> <span class="nx">comp</span><span class="p">;</span>
		<span class="c1">// To avoid creating side effects in render, stop the computation immediately</span>
		<span class="nx">Meteor</span><span class="p">.</span><span class="nx">defer</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">&amp;&amp;</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">)</span> <span class="p">{</span>
				<span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
				<span class="k">delete</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">});</span>
	<span class="p">},</span> <span class="nx">deps</span><span class="p">);</span>
	<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
		<span class="c1">// Let subsequent renders know we are mounted (render is committed).</span>
		<span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span> <span class="o">=</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">nonreactive</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">Tracker</span><span class="p">.</span><span class="nx">autorun</span><span class="p">((</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
				<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">reactiveFn</span><span class="p">(</span><span class="nx">c</span><span class="p">);</span>
				<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">skipUpdate</span> <span class="o">||</span> <span class="o">!</span><span class="nx">skipUpdate</span><span class="p">(</span><span class="nx">refs</span><span class="p">.</span><span class="nx">data</span><span class="p">,</span> <span class="nx">data</span><span class="p">))</span> <span class="p">{</span>
					<span class="nx">refs</span><span class="p">.</span><span class="nx">data</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
					<span class="nx">forceUpdate</span><span class="p">();</span>
				<span class="p">}</span>
			<span class="p">}));</span>
		<span class="p">}</span>
		<span class="k">return</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
			<span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">.</span><span class="nx">stop</span><span class="p">();</span>
			<span class="k">delete</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">comp</span><span class="p">;</span>
			<span class="nx">refs</span><span class="p">.</span><span class="nx">isMounted</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
		<span class="p">};</span>
	<span class="p">},</span> <span class="nx">deps</span><span class="p">);</span>
	<span class="k">return</span> <span class="nx">refs</span><span class="p">.</span><span class="nx">data</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">export</span> <span class="kd">function</span> <span class="nx">useTracker</span><span class="p">(</span><span class="nx">reactiveFn</span><span class="p">,</span> <span class="nx">deps</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">skipUpdate</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="nx">deps</span> <span class="o">===</span> <span class="kc">null</span> <span class="o">||</span> <span class="nx">deps</span> <span class="o">===</span> <span class="kc">undefined</span> <span class="o">||</span> <span class="o">!</span><span class="nb">Array</span><span class="p">.</span><span class="nx">isArray</span><span class="p">(</span><span class="nx">deps</span><span class="p">))</span> <span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">deps</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
			<span class="nx">skipUpdate</span> <span class="o">=</span> <span class="nx">deps</span><span class="p">;</span>
		<span class="p">}</span>
		<span class="k">return</span> <span class="nx">useTrackerNoDeps</span><span class="p">(</span><span class="nx">reactiveFn</span><span class="p">,</span> <span class="nx">skipUpdate</span><span class="p">);</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">useTrackerWithDeps</span><span class="p">(</span><span class="nx">reactiveFn</span><span class="p">,</span> <span class="nx">deps</span><span class="p">,</span> <span class="nx">skipUpdate</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="meteor-updates">Meteor updates</h2>

<p>Meteor updates are a simple matter of accepting the update and re-running the bundler:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> .meteor-client/app
meteor update
<span class="nb">cd</span> ../../
npm run meteor-client-bundle
</code></pre></div></div>]]></content><author><name></name></author><category term="meteor" /><category term="react-native" /><category term="javascript" /><summary type="html"><![CDATA[When developing a React Native app with a meteor backend, you may be tempted to make use of the meteor client found on npm. However, that is not the best way!]]></summary></entry><entry><title type="html">Fastest way to calculate distance between coordinates</title><link href="https://jamesloper.com/fastest-way-to-calculate-distance-between-two-coordinates" rel="alternate" type="text/html" title="Fastest way to calculate distance between coordinates" /><published>2021-08-20T00:00:00+00:00</published><updated>2021-08-20T00:00:00+00:00</updated><id>https://jamesloper.com/fastest-way-to-calculate-distance-between-two-coordinates</id><content type="html" xml:base="https://jamesloper.com/fastest-way-to-calculate-distance-between-two-coordinates"><![CDATA[<p><img src="/assets/haversine.png" alt="Latitude &amp; Longitude Diagram" class="banner" /></p>

<p>I’m always facing the problem of calculating the distance between two points on the surface of the Earth. Most of my readers will already be aware of the Haversine formula, so let’s dive in and see how many earth shattering speed enhancements we can make.</p>

<!--more-->

<h2 id="good-old-haversine">Good old haversine</h2>

<p>Haversine is a pretty solid way to calculate distances. Although it assumes the earth is a sphere, it sacrifices very little and reaches a good level of speed. Despite it being the more performant among the top distance algorithms, the haversine still involves 7 computationally expensive trigonometric functions (4 sines, 2 cosines, 1 arc tangent, 2 powers of two, and 2 square roots) and looks like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">R</span> <span class="o">=</span> <span class="mf">6378.137</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">toRad</span> <span class="o">=</span> <span class="nx">n</span> <span class="o">=&gt;</span> <span class="nx">n</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">/</span> <span class="mi">180</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">getDistance</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">dLat</span> <span class="o">=</span> <span class="nx">toRad</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">lat</span> <span class="o">-</span> <span class="nx">a</span><span class="p">.</span><span class="nx">lat</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">dLon</span> <span class="o">=</span> <span class="nx">toRad</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">lng</span> <span class="o">-</span> <span class="nx">a</span><span class="p">.</span><span class="nx">lng</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">aLat</span> <span class="o">=</span> <span class="nx">toRad</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">bLat</span> <span class="o">=</span> <span class="nx">toRad</span><span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">lat</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">f</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">dLat</span> <span class="o">/</span> <span class="mi">2</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">pow</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">dLon</span> <span class="o">/</span> <span class="mi">2</span><span class="p">),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">aLat</span><span class="p">)</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">bLat</span><span class="p">));</span>
    <span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">atan2</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="nx">f</span><span class="p">),</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">f</span><span class="p">));</span>
    <span class="k">return</span> <span class="nx">R</span> <span class="o">*</span> <span class="nx">c</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<blockquote>
  <p>I benchmarked the haversine taking 1.413s to process 5 million coordinate pairs.</p>
</blockquote>

<h2 id="optimizing-for-small-distances-the-fast-haversine">Optimizing for small distances: the fast haversine</h2>

<p>My own use case allowed me to take some additional shortcuts. I got rid of the Sin calculations. On top of that, the Cos of two close latitudes is approximately the Cos of its average. That allowed me to remove one Cos and assume some error. Finally, the two Pow were inlined, so no need to call an external generic power function if only to multiply a number by itself. The final algorithm, with some additional inlining to optimize even more, looks like this:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">R</span> <span class="o">=</span> <span class="mf">6378.137</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">PI_360</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">/</span> <span class="mi">360</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">getDistance</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">cLat</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">((</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span> <span class="o">+</span> <span class="nx">b</span><span class="p">.</span><span class="nx">lat</span><span class="p">)</span> <span class="o">*</span> <span class="nx">PI_360</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">dLat</span> <span class="o">=</span> <span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">lat</span> <span class="o">-</span> <span class="nx">a</span><span class="p">.</span><span class="nx">lat</span><span class="p">)</span> <span class="o">*</span> <span class="nx">PI_360</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">dLon</span> <span class="o">=</span> <span class="p">(</span><span class="nx">b</span><span class="p">.</span><span class="nx">lng</span> <span class="o">-</span> <span class="nx">a</span><span class="p">.</span><span class="nx">lng</span><span class="p">)</span> <span class="o">*</span> <span class="nx">PI_360</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">f</span> <span class="o">=</span> <span class="nx">dLat</span> <span class="o">*</span> <span class="nx">dLat</span> <span class="o">+</span> <span class="nx">cLat</span> <span class="o">*</span> <span class="nx">cLat</span> <span class="o">*</span> <span class="nx">dLon</span> <span class="o">*</span> <span class="nx">dLon</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">atan2</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="nx">f</span><span class="p">),</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="nx">f</span><span class="p">));</span>   
    <span class="k">return</span> <span class="nx">R</span> <span class="o">*</span> <span class="nx">c</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<blockquote>
  <p>Benchmarking 5 million coordinate pairs took 303.719ms. We are doing well with impressive gains. This one took only 21% of the time compared to baseline.</p>
</blockquote>

<h2 id="reaching-the-end-game-the-sloppy-haversine">Reaching the end-game, the sloppy haversine</h2>

<p>After finding the fast haversine formula, I could not stand still. I knew there was one more way to push the formula even further by revisiting our good friend trigonometry. By now we know that haversine exists to accommodate the earth’s spherical math. However when we are looking close to the earth’s surface it no longer appears as a sphere and instead looks flat. So if we can find a factor to multiply by the longitude delta, and a factor to multiply by the latitude delta, we can use trigonometry as if we are dealing with a flat plane. This is what is known as a projection which is when you flatten the latitude and longitude into x and y coordinates that represent kilometers. That code is this magnificent sloppy boy:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">kRad</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">/</span> <span class="mi">180</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">getDistance</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">kx</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span> <span class="o">*</span> <span class="nx">kRad</span><span class="p">)</span> <span class="o">*</span> <span class="mf">111.321</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">dx</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lng</span> <span class="o">-</span> <span class="nx">b</span><span class="p">.</span><span class="nx">lng</span><span class="p">)</span> <span class="o">*</span> <span class="nx">kx</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">dy</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span> <span class="o">-</span> <span class="nx">b</span><span class="p">.</span><span class="nx">lat</span><span class="p">)</span> <span class="o">*</span> <span class="mf">111.139</span><span class="p">;</span>
    <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">((</span><span class="nx">dx</span> <span class="o">*</span> <span class="nx">dx</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">dy</span> <span class="o">*</span> <span class="nx">dy</span><span class="p">));</span>
<span class="p">};</span>
</code></pre></div></div>

<blockquote>
  <p>This new algorithm takes a mere square root and cosine call and completes 5 million coordinate pairs in a break neck speed of 215.79ms which is only 15% of the baseline!</p>
</blockquote>

<h2 id="going-even-further-for-a-specific-use-case">Going even further for a specific use case</h2>

<p>Usually we want to calculate the distance of your current location to many other locations, so your current location will be the same for each calculation. In the sloppy haversine, the variable <code class="language-plaintext highlighter-rouge">kx</code>, which is the factor to multiply the <code class="language-plaintext highlighter-rouge">x</code> coordinate by, will not change unless the first location changes. We can make a closure that reuses the cosine, so that each individual comparison uses only <strong>one square root call</strong>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">kRad</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">/</span> <span class="mi">180</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">ruler</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">kx</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span> <span class="o">*</span> <span class="nx">kRad</span><span class="p">)</span> <span class="o">*</span> <span class="mf">111.321</span><span class="p">;</span>
    <span class="k">return</span> <span class="p">(</span><span class="nx">b</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">dx</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lng</span> <span class="o">-</span> <span class="nx">b</span><span class="p">.</span><span class="nx">lng</span><span class="p">)</span> <span class="o">*</span> <span class="nx">kx</span><span class="p">;</span>
        <span class="kd">const</span> <span class="nx">dy</span> <span class="o">=</span> <span class="p">(</span><span class="nx">a</span><span class="p">.</span><span class="nx">lat</span> <span class="o">-</span> <span class="nx">b</span><span class="p">.</span><span class="nx">lat</span><span class="p">)</span> <span class="o">*</span> <span class="mf">111.139</span><span class="p">;</span>
        <span class="k">return</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sqrt</span><span class="p">((</span><span class="nx">dx</span> <span class="o">*</span> <span class="nx">dx</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">dy</span> <span class="o">*</span> <span class="nx">dy</span><span class="p">));</span>
    <span class="p">}</span> 
<span class="p">};</span>
</code></pre></div></div>

<h2 id="removing-the-square-root">Removing the square root</h2>

<p>Hilariously, if you are only comparing distances and not displaying them, there is no need for the square root. Simply comparing the result of <code class="language-plaintext highlighter-rouge">(dx * dx) + (dy * dy)</code> will do. And with the last math call gone, we can plainly see here that with just a little work, we can avoid doing any work, which is kind of my life philosophy.</p>

<p>So, we can conclude that haversine is very generalized, and you should choose algorithms knowing how they work, not just blindly importing them!</p>]]></content><author><name></name></author><category term="javascript" /><category term="gps" /><category term="location" /><category term="algorithm" /><summary type="html"><![CDATA[I’m always facing the problem of calculating the distance between two points on the surface of the Earth. Most of my readers will already be aware of the Haversine formula, so let’s dive in and see how many earth shattering speed enhancements we can make.]]></summary></entry><entry><title type="html">My hackathon submission: Emissions Queen</title><link href="https://jamesloper.com/hackathon-emissions-queen" rel="alternate" type="text/html" title="My hackathon submission: Emissions Queen" /><published>2021-06-30T00:00:00+00:00</published><updated>2021-06-30T00:00:00+00:00</updated><id>https://jamesloper.com/hackathon-emissions-queen</id><content type="html" xml:base="https://jamesloper.com/hackathon-emissions-queen"><![CDATA[<p><img src="/assets/queen.png" alt="Emissions Queen" class="banner" /></p>

<p>For DigitalOcean’s 2021 hackathon I set out to create a dashboard for my Emissions station in Duluth, GA.</p>

<!--more-->

<p>You can check progress on the publicly accessible website <a href="https://queen.jamesloper.com">queen.jamesloper.com</a></p>

<h2 id="i-am-aiming-to-build-the-following-features">I am aiming to build the following features…</h2>

<ol>
  <li>Accounts with ability to link devices</li>
  <li>Webcam view of the interior of the station from Wyze cam</li>
  <li>Screen cast of the emissions computer screen (for diagnostics)</li>
  <li>Call logs for the business phone line (provided by Twilio)</li>
  <li>To determine the exact time the employee opened and closed for the day, I am making an integration with myQ to see when the garage door is open or closed</li>
  <li>To check profit, I am making a scraper for the government provided dashboard website</li>
</ol>

<h2 id="development--digitalocean">Development &amp; DigitalOcean</h2>

<p>The mongo database will be hosted on DigitalOcean managed MongoDB, and the app will be served by a DigitalOcean droplet. I will run a copy of the development environment on my laptop and then deploy to “production”, but to ease the transition, I will connect my development environment straight to the MongoDB since the beginning. This way I don’t have to maintain two databases.</p>

<h2 id="how-will-i-determine-the-profit">How will I determine the profit?</h2>

<p>Each car that buys an emissions test uses up a “ticket” provided by the government. This is recurring cost for the emissions business, and since each test is a fixed cost, profit can be calculated from just the number of used tickets. If I can scrape the government website, I can calculate profit to a high degree of accuracy.</p>

<h2 id="how-will-the-webcamsstreams-work">How will the webcams/streams work?</h2>

<p>Depending on the protocol, it can be easy or hard. For the screen cast, it’s easy. I bought a <a href="https://amzn.to/3drkRJ2">capture card with pass-thru</a> to grab the HDMI signal running to the emissions station screen. A <a href="https://amzn.to/3y3n4SY">cheap computer</a> running <a href="ubuntu-server-install-mjpg-streamer">mjpg-streamer</a> exposes the feed as a URL. To embed that in a webpage, all I have to do is put an <code class="language-plaintext highlighter-rouge">&lt;img&gt;</code> tag with the url set as the <code class="language-plaintext highlighter-rouge">src</code>. Any URL pointing to a MJPEG stream can also be embedded into a web page this way.</p>

<p>I also want to get some networked webcams, so I chose <a href="https://amzn.to/3dtEUXv">Wyze Cam V2</a>. It is capable of serving an RTSP stream, but I had to convert that to something web browsers can view. For this I made an <a href="https://github.com/jamesloper/emissions/blob/main/server/imports/rest/proxy-video.js">RTSP to MJPEG proxy server</a>. I hand off most of the work to FFMPEG, streaming the output from stdout to the client, however care must be taken to build a buffer and flush it upon each frame.</p>

<h2 id="how-are-the-call-logs-set-up">How are the call logs set up?</h2>

<p>When adding a Twilio account, I fetch the historical logs and massage the data into my own format before inserting it into the Mongo DB. From that point, a webhook is enabled so that whenever Twilio gets a phone call, they will make an HTTP call to my REST endpoint. This does mean that while developing the app, I have to make my computer publicly accessible, for example by using <a href="https://ngrok.com/">ngrok</a> for this.</p>

<h2 id="how-will-the-garage-door-sensor-be-set-up">How will the garage door sensor be set up?</h2>

<p>I integrated myQ and will install a <a href="https://amzn.to/3hlSflA">smart garage door sensor</a> and will query it every 5 minutes to see if it is up or down.</p>]]></content><author><name></name></author><category term="digitalocean" /><category term="hackathon" /><category term="mongo" /><category term="meteor" /><summary type="html"><![CDATA[For DigitalOcean’s 2021 hackathon I set out to create a dashboard for my Emissions station in Duluth, GA.]]></summary></entry><entry><title type="html">MongoDB setup guide for Ubuntu 20</title><link href="https://jamesloper.com/mongodb-setup-guide-ubuntu-20" rel="alternate" type="text/html" title="MongoDB setup guide for Ubuntu 20" /><published>2021-04-05T00:00:00+00:00</published><updated>2021-04-05T00:00:00+00:00</updated><id>https://jamesloper.com/mongodb-setup-guide-ubuntu-20</id><content type="html" xml:base="https://jamesloper.com/mongodb-setup-guide-ubuntu-20"><![CDATA[<p><img src="/assets/mongodb-ssl.png" alt="MongoDB SSL" class="banner" /></p>

<p>While hosted mongodb solutions exist, they aren’t for everyone. Scaling, security, and reliability is just one tutorial away. In this fairly deep tutorial we cover everything from the initial installation to optional steps such as SSL and a secondary replica. This guide is targeted for Ubuntu 20.
<!--more--></p>

<h2 id="quick-links">Quick links</h2>

<ol>
  <li><a href="#getting-started">Getting started</a></li>
  <li><a href="#enable-automatic-startup-mongod-server-on-server-boot">Enable automatic startup mongod server on server boot</a></li>
  <li><a href="#create-the-admin-user">Create the admin user</a></li>
  <li><a href="#enable-ssl">Enable SSL</a></li>
  <li><a href="#adding-a-secondary">Adding a secondary</a></li>
  <li><a href="#enable-swap">Enable swap</a></li>
  <li><a href="#simple-mongodb-backups-with-mongodump">Simple MongoDB backups with mongodump</a></li>
  <li><a href="#diagnostics-cheat-sheet">Diagnostics cheat sheet</a></li>
</ol>

<h2 id="getting-started">Getting started</h2>

<p>In this guide, the primary will be <code class="language-plaintext highlighter-rouge">db.example.com</code>. Start by logging in and performing the apt ritual. You should already set up a user, in this case my user is <code class="language-plaintext highlighter-rouge">ubuntu</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh ubuntu@db.example.com
<span class="nb">sudo </span>apt-get <span class="nb">install </span>gnupg curl
<span class="nb">echo</span> <span class="s2">"deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse"</span> | <span class="nb">sudo tee</span> /etc/apt/sources.list.d/mongodb-org-8.0.list
<span class="nb">sudo </span>apt update <span class="o">&amp;&amp;</span> <span class="nb">sudo </span>apt upgrade
<span class="nb">sudo </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> mongodb-org
</code></pre></div></div>

<p><strong>Optional:</strong> Create a data directory. You’ll want to do this so that the database doesn’t reside on the boot drive. This is a good practice! XFS is the filesystem of choice for mongodb data directories.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> /mnt/db/mongodb
<span class="nb">sudo chown</span> <span class="nt">-R</span> mongodb:mongodb /mnt/db/mongodb
</code></pre></div></div>

<p>Create a barebones configuration just to get it functioning. Edit <code class="language-plaintext highlighter-rouge">/etc/mongod.conf</code>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">storage</span><span class="pi">:</span>
  <span class="na">dbPath</span><span class="pi">:</span> <span class="s">/mnt/db/mongodb</span>
  
<span class="na">net</span><span class="pi">:</span>
  <span class="na">bindIp</span><span class="pi">:</span> <span class="s">0.0.0.0</span>
  
<span class="na">replication</span><span class="pi">:</span>
  <span class="na">replSetName</span><span class="pi">:</span> <span class="s">rs0</span>
  <span class="na">oplogSizeMB</span><span class="pi">:</span> <span class="m">100</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">dbPath</code> should be set to the path you made. Or you can just remove the whole storage section if you are going to use the default.</li>
  <li><code class="language-plaintext highlighter-rouge">0.0.0.0</code> makes your server accessible via the web.</li>
</ul>

<p>Restart <code class="language-plaintext highlighter-rouge">mongod</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>service mongod restart
</code></pre></div></div>

<h2 id="enable-automatic-startup-mongod-server-on-server-boot">Enable automatic startup mongod server on server boot</h2>

<p>Register it with <code class="language-plaintext highlighter-rouge">systemctl</code> to start when your server boots</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl <span class="nb">enable </span>mongod
</code></pre></div></div>

<p>At this point you can connect to the db, but it’s also wide open for attackers to take over! Next up we will deal with security.</p>

<h2 id="create-the-admin-user">Create the admin user</h2>

<p>In this section, you will configure your database to only accept username &amp; password authentication. While we are in here we might as well take care of initializing the replica set as well. Using your existing SSH session, connect to your database and create an <code class="language-plaintext highlighter-rouge">admin</code>
user:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongosh
use <span class="nb">local
</span>rs.initiate<span class="o">()</span>
use admin

db.createUser<span class="o">({</span>
  user: <span class="s1">'admin'</span>,
  <span class="nb">pwd</span>: <span class="s1">'password'</span>,
  roles: <span class="o">[{</span>role: <span class="s1">'root'</span>, db: <span class="s1">'admin'</span><span class="o">}]</span>
<span class="o">})</span>
</code></pre></div></div>

<p>Edit <code class="language-plaintext highlighter-rouge">/etc/mongod.conf</code> again! Specify a security policy. This one is absolutely required if your server is bound to 0.0.0.0! Once done, you must authenticate every time (even through localhost!)</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">security</span><span class="pi">:</span>
  <span class="na">authorization</span><span class="pi">:</span> <span class="s2">"</span><span class="s">enabled"</span>
  <span class="na">keyFile</span><span class="pi">:</span> <span class="s">/data/key</span>
</code></pre></div></div>

<h2 id="create-key-file">Create key file</h2>
<p>This step is kind of a song and dance but you just have to do it to appease the mongo gods.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo touch</span> /data/key
<span class="nb">sudo chmod </span>400 /data/key
<span class="nb">sudo </span>nano /data/key
</code></pre></div></div>
<p>And just put some random string in this file!</p>

<p>Restart mongod:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl restart mongod
<span class="nb">sudo </span>systemctl status mongod
</code></pre></div></div>

<p>Try it out! You want to make sure you can log in.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongo mongodb://admin:password@db.example.com
</code></pre></div></div>

<h2 id="enable-web-server">Enable web server</h2>

<p>LetsEncrypt requires your server to respond to a challenge in order to fulfill a certificate request, so we’ll need a tiny web server to answer their challenge:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>add-apt-repository ppa:nginx/stable
<span class="nb">sudo </span>apt-get update
<span class="nb">sudo </span>apt <span class="nb">install </span>nginx
</code></pre></div></div>

<p>At this point, navigating to <code class="language-plaintext highlighter-rouge">db.example.com</code> will result in the demo page for Nginx. Nothing further is really required to set up Nginx, and quite frankly no other software should even be running on your production database server.</p>

<h2 id="install-certbot--certificate">Install certbot &amp; certificate</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install </span>certbot python3-certbot-nginx
certbot <span class="nt">--nginx</span> <span class="nt">-d</span> db.example.com
</code></pre></div></div>

<p>At this point, you should be able to visit the NGINX page again, but this time it will be served over HTTPS! Next, we need to set up automatic renewal and application of the cert to MongoDB. Set up a bash script that will concatenate your NGINX certificates together to create your MongoDB server certificate:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~
nano renew-mongo-cert.sh
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="nv">DOMAIN</span><span class="o">=</span>db.example.com
<span class="nv">newestFull</span><span class="o">=</span><span class="si">$(</span><span class="nb">ls</span> <span class="nt">-v</span> /etc/letsencrypt/archive/<span class="nv">$DOMAIN</span>/fullchain<span class="k">*</span>.pem | <span class="nb">tail</span> <span class="nt">-n</span> 1<span class="si">)</span>
<span class="nv">newestPriv</span><span class="o">=</span><span class="si">$(</span><span class="nb">ls</span> <span class="nt">-v</span> /etc/letsencrypt/archive/<span class="nv">$DOMAIN</span>/privkey<span class="k">*</span>.pem | <span class="nb">tail</span> <span class="nt">-n</span> 1<span class="si">)</span>
<span class="nb">cat</span> <span class="nv">$newestFull</span> <span class="nv">$newestPriv</span> <span class="o">&gt;</span> /etc/ssl/mongo.pem
service mongod restart
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod</span> +x renew-mongo-cert.sh
</code></pre></div></div>

<p>Enable SSL in the mongo server configuration in <code class="language-plaintext highlighter-rouge">/etc/mongod.conf</code> by adding this under the <code class="language-plaintext highlighter-rouge">net</code> declaration:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">tls</span><span class="pi">:</span>
    <span class="na">mode</span><span class="pi">:</span> <span class="s">requireTLS</span>
    <span class="na">certificateKeyFile</span><span class="pi">:</span> <span class="s">/etc/ssl/mongo.pem</span>
</code></pre></div></div>

<p>Run the script for the first time:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./renew-mongo-cert.sh
</code></pre></div></div>

<p>Set the file permissions for the outputted pem file. This is only needed one time.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">chmod </span>600 /etc/ssl/mongo.pem
<span class="nb">chown </span>mongodb:mongodb /etc/ssl/mongo.pem
</code></pre></div></div>

<p>Add the script as a cron job with <code class="language-plaintext highlighter-rouge">crontab -e</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 0 1 * * /root/renew-mongo-cert.sh
</code></pre></div></div>

<p>Reboot, and now you can connect through SSL. Note that the URL now contains the <code class="language-plaintext highlighter-rouge">ssl</code> param:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongo mongodb://admin:password@db.example.com/?ssl<span class="o">=</span><span class="nb">true</span>
</code></pre></div></div>

<h2 id="adding-a-secondary">Adding a secondary</h2>

<p>Follow the same process above to get mongo installed and configured, switching out <code class="language-plaintext highlighter-rouge">db.example.com</code> with your secondary’s hostname.</p>

<p>All nodes in a cluster must share the same secret key– that’s how they authenticate with each other. We’ll make that file next. Do this step on both servers!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"your-secret-key"</span> <span class="o">&gt;</span> /data/key
</code></pre></div></div>

<p>Edit <code class="language-plaintext highlighter-rouge">/etc/mongod.conf</code></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> security:
<span class="o">&gt;</span>  keyFile: /data/key
</code></pre></div></div>

<p>Then restart the mongo server again</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service mongod restart
</code></pre></div></div>

<p>Next, log back in to the mongo shell.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongo mongodb://admin:password@db.example.com/?ssl=true
</code></pre></div></div>

<p>The following mongo shell script is to have a <code class="language-plaintext highlighter-rouge">secondary</code> that your BI tools could beat up without affecting your <code class="language-plaintext highlighter-rouge">primary</code>. That’s why hidden is set to 1 and not 0. This ensures that the node can not become the primary in the event of a primary malfunction.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rs.add<span class="o">({</span>host: <span class="s2">"secondary.example.com"</span>, priority: 0, votes: 0, hidden: 1<span class="o">})</span>
rs.slaveOk<span class="o">()</span>
</code></pre></div></div>

<p>If you want to provide more hosts for your app to connect to, you instead want to make them visible. The minimum number of nodes in a cluster jumps from 1 straight to 3, otherwise a cluster of 2 can not perform a vote to decide which node will become primary.</p>

<h2 id="enable-swap">Enable swap</h2>

<p>In the case of large load, having a few gigs of swap space can help your database not crash. Unfortunately turning swap on can mask critical memory issues which would be better solved by adding more ram. It’s always up to you to determine if you want to use swap or not. If you want to go down that route, here is how.</p>

<p><strong>Tip:</strong> You can verify at any time that your swap is working by running <code class="language-plaintext highlighter-rouge">sudo swapon --show</code></p>

<p>Run the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>fallocate <span class="nt">-l</span> 2G /swapfile
<span class="nb">sudo chmod </span>600 /swapfile
<span class="nb">sudo </span>mkswap /swapfile
<span class="nb">sudo </span>swapon /swapfile
</code></pre></div></div>

<p>Now you have swap space, but it won’t persist after reboot. Fix that by editing your fstab file with <code class="language-plaintext highlighter-rouge">sudo nano /etc/fstab</code> and paste the following <code class="language-plaintext highlighter-rouge">/swapfile swap swap defaults 0 0</code></p>

<h2 id="simple-mongodb-backups-with-mongodump">Simple MongoDB backups with mongodump</h2>

<p>This is probably a horrible solution for a database over 1 gig, but I just got it functional so I will share it in the hopes that someone can benefit from it.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>backups
<span class="nb">mkdir </span>backups/hourly
<span class="nb">cd </span>backups
nano backup.sh
</code></pre></div></div>

<p>Create a bash script like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
mongodump <span class="nt">--uri</span><span class="o">=</span><span class="s2">"mongodb://user:pass@localhost:27017/test"</span> <span class="nt">--out</span><span class="o">=</span><span class="s2">"dump"</span>
zip <span class="nt">-r</span> <span class="s2">"hourly/</span><span class="si">$(</span><span class="nb">date</span> +<span class="s2">"%Y-%m-%d %I:%M%p"</span><span class="si">)</span><span class="s2">.zip"</span> dump
find hourly/<span class="k">*</span> <span class="nt">-mtime</span> +1 <span class="nt">-exec</span> <span class="nb">rm</span> <span class="o">{}</span> <span class="se">\;</span>
</code></pre></div></div>

<p>This script creates a new backup and zips it, and deposits it in the hourly folder, then deletes any hourly backups that are older than a day. You can make this run hourly with <code class="language-plaintext highlighter-rouge">crontab -e</code></p>

<h2 id="diagnostics-cheat-sheet">Diagnostics cheat sheet</h2>

<ul>
  <li>Clear logs with <code class="language-plaintext highlighter-rouge">echo "" &gt; /var/log/mongodb/mongod.log</code></li>
  <li>Get logs with <code class="language-plaintext highlighter-rouge">tail /var/log/mongodb/mongod.log</code></li>
  <li>Edit config at <code class="language-plaintext highlighter-rouge">nano /etc/mongod.conf</code></li>
  <li>Key file should be 600 permissions</li>
  <li>mongodb user should own <code class="language-plaintext highlighter-rouge">/var/log/mongodb</code></li>
  <li>Unlink socket with <code class="language-plaintext highlighter-rouge">rm /tmp/mongodb-27017.sock</code></li>
  <li>Start mongod service with <code class="language-plaintext highlighter-rouge">service mongod start</code></li>
</ul>]]></content><author><name></name></author><category term="ubuntu" /><category term="it" /><category term="mongo" /><category term="ssl" /><summary type="html"><![CDATA[While hosted mongodb solutions exist, they aren’t for everyone. Scaling, security, and reliability is just one tutorial away. In this fairly deep tutorial we cover everything from the initial installation to optional steps such as SSL and a secondary replica. This guide is targeted for Ubuntu 20.]]></summary></entry></feed>