<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Mohl</title>
    <description>The latest articles on DEV Community by David Mohl (@dvcrn).</description>
    <link>https://dev.to/dvcrn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F738876%2F25629ae2-669c-4c6b-87f3-081e9c384ec5.jpeg</url>
      <title>DEV Community: David Mohl</title>
      <link>https://dev.to/dvcrn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dvcrn"/>
    <language>en</language>
    <item>
      <title>Building your own distributed pocket ChatGPT in Elixir with GenServer</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Mon, 13 Mar 2023 10:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/building-your-own-distributed-pocket-chatgpt-in-elixir-with-genserver-dno</link>
      <guid>https://dev.to/dvcrn/building-your-own-distributed-pocket-chatgpt-in-elixir-with-genserver-dno</guid>
      <description>&lt;p&gt;&lt;em&gt;From my blog: &lt;a href="https://david.coffee/mini-chatgpt-in-elixir-and-genserver/"&gt;https://david.coffee/mini-chatgpt-in-elixir-and-genserver/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently built an &lt;a href="https://github.com/dvcrn/ex_openai"&gt;Elixir SDK for OpenAI APIs&lt;/a&gt; for a project I was working on to automatically fix and correct Japanese grammar (&lt;a href="https://fixmyjp.d.sh"&gt;https://fixmyjp.d.sh&lt;/a&gt;). The SDK is actually fully auto-generated with metaprogramming which is something I wanted to do in Elixir for a while, but that’s not the topic of today’s post.&lt;/p&gt;

&lt;p&gt;Today, let’s use that SDK for something fun and build a smol ChatGPT service for your shell from scratch, in true Elixir fashion!&lt;/p&gt;

&lt;p&gt;The end result will be an &lt;code&gt;ai&lt;/code&gt; shell command that is usable from wherever we want, to send messages to a distributed &lt;code&gt;gptserver&lt;/code&gt; node for interacting with ChatGPT, that can either be on the same computer, or anywhere on the same network&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QEY4YDmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2023-03-your-own-mini-chatgpt-in-elixir/finalshell.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QEY4YDmk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2023-03-your-own-mini-chatgpt-in-elixir/finalshell.png" alt="finalshell" width="880" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick refresher: GenServers, and how state in Elixir is implemented
&lt;/h2&gt;

&lt;p&gt;Elixir is an &lt;strong&gt;immutable&lt;/strong&gt; functional programming language, meaning you can’t mutate things. If you do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you are not mutating &lt;code&gt;a&lt;/code&gt; to be a+1, instead you create a new &lt;code&gt;a&lt;/code&gt; with the value of a+1. That means you can’t do things like this in Elixir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{}&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead, you would do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;%{}&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"bar"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;again, creating a new &lt;code&gt;a&lt;/code&gt; with a copy of the previous a, but one item added. The old &lt;code&gt;a&lt;/code&gt; is not mutated, it still exists until the garbage collector gets rid of it.&lt;/p&gt;

&lt;p&gt;So how tf do you actually keep any state across different modules then? From the sounds of it, you would need to constantly do something with a value, you can’t just create an object that different parts of the code can mutate and share.&lt;/p&gt;

&lt;p&gt;In Elixir/Erlang-land, instead of having a shared object/container somewhere, we use &lt;em&gt;processes&lt;/em&gt; managed within the BEAM VM, and the most common way to define those processes is &lt;code&gt;GenServer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;GenServer is a behaviour that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;

    &lt;span class="c1"&gt;# stuff that's running inside a separate process&lt;/span&gt;

    &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# stuff that's running inside the main process&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 0 + 2&lt;/span&gt;
&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 2 + 3&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To hold state, we effectively created a new process whose whole purpose is to hold a value in memory, then listen for a message &lt;code&gt;:add&lt;/code&gt;, to then add whatever got passed to create a &lt;strong&gt;new value&lt;/strong&gt; (remember? we can’t mutate things) to hold in memory and start listening for new messages again. Imagine it as a (&lt;em&gt;very&lt;/em&gt; simplified representation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startValue&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;waitForUserInput&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// yay, no variables&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Level 1: ChatGPT in a GenServer
&lt;/h2&gt;

&lt;p&gt;Okay now that we remembered how to handle state in Elixir, we know what to do to get a ChatGPT-like system going: &lt;code&gt;Wait for user input -&amp;gt; append to messages so far -&amp;gt; send to OpenAI -&amp;gt; output -&amp;gt; wait for user input again&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let’s summon a new mix project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mix new mixgpt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;add my &lt;code&gt;ex_openai&lt;/code&gt; library as deps to &lt;code&gt;mix.exs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ex_openai&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.0.2"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(follow the &lt;a href="https://github.com/dvcrn/ex_openai"&gt;instructions for configuration&lt;/a&gt; with an API key)&lt;/p&gt;

&lt;p&gt;and conjure up a basic GenServer with a new &lt;code&gt;{:msg, m}&lt;/code&gt; message listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}&lt;/span&gt; &lt;span class="c1"&gt;# start value is empty list&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make our life easier, we’ll also add a &lt;code&gt;start_link&lt;/code&gt; function to start the thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="bp"&gt;__MODULE__&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:gptserver&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;name: :gptserver&lt;/code&gt; argument here - we’re giving the GenServer a unique name so that we don’t need to worry about remembering its PID somewhere.&lt;/p&gt;

&lt;p&gt;To send a message to this new genserver, we’ll also create a new &lt;code&gt;send()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:gptserver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;thanks to &lt;code&gt;name: :gptserver&lt;/code&gt; we already know how to reach this instance, so we no longer need the PID to be passed. Now, all we have to do is &lt;code&gt;Mixgpt.send "hi!"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the actual &lt;code&gt;handle_call&lt;/code&gt;, what we have to do is: construct a new OpenAI ChatGPT message with the passed message, send it to the OpenAI API, and add whatever it returned to the internal state of this server (remember from above? how to handle state). Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;new_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="p"&gt;%&lt;/span&gt;&lt;span class="no"&gt;ExOpenAI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ChatCompletionRequestMessage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;content:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;role:&lt;/span&gt; &lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="s2"&gt;"user"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nv"&gt;@impl&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# create temporary "msgs" with existing list + the new message from the user &lt;/span&gt;
  &lt;span class="c1"&gt;# effectively appending the new message to the existing messages&lt;/span&gt;
  &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;

      &lt;span class="c1"&gt;# call OpenAI ChatGPT API with the new msgs list&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="no"&gt;ExOpenAI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_chat_completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"gpt-3.5-turbo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c1"&gt;# return the content of the result, add it to the msgs list and continue the GenServer loop with {:reply}&lt;/span&gt;
        &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# second value is what's returned, third value is the new state of this server&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msgs&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cool thing about Elixir and other functional languages is that they usually come with a REPL, or a shell that you can use to interact with different bits and pieces of your program. In Elixir, that’s &lt;code&gt;iex&lt;/code&gt;. Run &lt;code&gt;iex -S mix&lt;/code&gt;, wait until it starts, then try to interact with our new GenServer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.280.0&amp;gt;}&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt; &lt;span class="s2"&gt;"Hi!"&lt;/span&gt;
&lt;span class="s2"&gt;"Hello! How can I assist you today?"&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt; &lt;span class="s2"&gt;"tell me a joke in 5 words"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Why did the tomato blush?&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, now we already got our own pocket ChatGPT that we can access through mix, that was pretty quick, wasn’t it? Let’s make it a bit more automatic and close the loop. To wait for user input, we can use &lt;code&gt;IO.gets&lt;/code&gt;, let’s use that in a new function to directly ask for the next message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;wait_for_input_and_send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# print the message out to have it nicely formatted&lt;/span&gt;

  &lt;span class="n"&gt;wait_for_input_and_send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# recurse &lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;recompile&lt;/code&gt; into iex to recompile the project and try again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_input_and_send&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Hi&lt;/span&gt;
&lt;span class="no"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt; &lt;span class="n"&gt;assist&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;today?&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Do&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;like&lt;/span&gt; &lt;span class="no"&gt;Elixir&lt;/span&gt;&lt;span class="err"&gt;?&lt;/span&gt;
&lt;span class="no"&gt;As&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="no"&gt;AI&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;I&lt;/span&gt; &lt;span class="n"&gt;don&lt;/span&gt;&lt;span class="s1"&gt;'t have personal preferences or feelings. However, I can tell you that Elixir is a popular programming language among developers and has some unique features that make it stand out, such as its scalability, fault tolerance, and ease of use for distributed systems.
&amp;gt; what was the first message I sent you?
The first message you sent me was "hi".
&amp;gt; 

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Level 2: Distributed ChatGPT service
&lt;/h2&gt;

&lt;p&gt;The example above is easy to do in any programming language, but let’s go one step further and do something cool that you can do beautifully in Elixir and Erlang, but not as easily in other programming languages: &lt;strong&gt;Let’s turn this thing into a proper distributed system!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a reminder, our mini chatgpt is running inside a &lt;code&gt;server&lt;/code&gt;! We did this because that’s how state is handled in Elixir. Even though we created the interface so that we can easily just call &lt;code&gt;send("why is the sky green?")&lt;/code&gt;, on the back it still sends a new message to this gpt-server, then waits until it responds. It just does this so transparently that we don’t even notice.&lt;/p&gt;

&lt;p&gt;But what’s even cooler: GenServers can already be accessed from other nodes within same BEAM cluster, so we &lt;em&gt;already have&lt;/em&gt; something that is usable over a network! Let’s take a look at how to do that, with only a little bit of black magic.&lt;/p&gt;

&lt;p&gt;Close the iex shell we had open, and create a new one, but this time with the &lt;code&gt;--name&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;gptserver&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="no"&gt;S&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt; 
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Mixgpt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#PID&amp;lt;0.276.0&amp;gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--name&lt;/code&gt; tells elixir to start a distributed node with the given name, on the given network. So we created a distributed node &lt;code&gt;gptserver&lt;/code&gt; on the network &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In a new terminal window, start another iex session, this time without &lt;code&gt;-S mix&lt;/code&gt;, but still with &lt;code&gt;--name&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iex &lt;span class="nt"&gt;--name&lt;/span&gt; client@127.0.0.1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To now execute things on another cluster node, Elixir provides the &lt;code&gt;Node.spawn_link&lt;/code&gt; function among other things. With this you could do stuff like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spawn_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:"gptserver@127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;IO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hi there, I'm running on another node"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we already have a GenServer running, so no need for that and just hit the GenServer directly. This is also very easy to do, just by changing the &lt;code&gt;GenServer.call(:gptserver, {:msg, msg})&lt;/code&gt; call to also include the node information where &lt;code&gt;:gptserver&lt;/code&gt; is running: &lt;code&gt;{:gptserver, :"gptserver@127.0.0.1"}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;GenServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;:gptserver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:"gptserver@127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Helloo, anyone there?"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="s2"&gt;"Hello! Yes, I'm here. How can I assist you?"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you realize what just happened? Our completely separate &lt;code&gt;iex&lt;/code&gt; shells just connected to each other to send a message to the GenServer running under the &lt;code&gt;:gptserver&lt;/code&gt; name, which then hits the OpenAI API, returns the data, and relays it back to the other &lt;code&gt;iex&lt;/code&gt; shell!&lt;/p&gt;

&lt;p&gt;We just created a distributed ChatGPT service on our local network (well, localhost), all without any extra setup required! This even works across machines, as long as the nodes are running reachable on the same network :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Final step: A global shell command
&lt;/h2&gt;

&lt;p&gt;You can already guess what comes next. We can run Elixir code directly with &lt;code&gt;Elixir -e "IO.puts \"Hi\""&lt;/code&gt;. The last lego piece missing is hooking this up to a global shell command, so that we can access our mini-ChatGPT whenever we need through a terminal, while retaining previous context.&lt;/p&gt;

&lt;p&gt;To package it all up into a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;elixir &lt;span class="nt"&gt;--name&lt;/span&gt; client@127.0.0.1 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"GenServer.call({:gptserver, :'gptserver@127.0.0.1'}, {:msg, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hi&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}) |&amp;gt; IO.puts"&lt;/span&gt;
Hello! How can I assist you today?

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s still the same call as before, except we’re running it through &lt;code&gt;elixir -e ""&lt;/code&gt;, and pipe the result into &lt;code&gt;IO.puts&lt;/code&gt; to write it to the shell. This is still using the same GenServer that’s still running (unless you shut it down), and it still retains the same message history from earlier.&lt;/p&gt;

&lt;p&gt;Lastly, package into a bash function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ai&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; elixir &lt;span class="nt"&gt;--name&lt;/span&gt; client@127.0.0.1 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"GenServer.call({:gptserver, :'gptserver@127.0.0.1'}, {:msg, Enum.join(System.argv, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)}) |&amp;gt; IO.puts"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tadaa! We now have a system-wide (or network-wide) mini-ChatGPT that is fully &lt;strong&gt;stateful&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ai is the sky blue?
Yes, on clear days, the daytime sky typically appears blue due to the scattering of sunlight by Earth&lt;span class="s1"&gt;'s atmosphere.
$ ai what is the last message I sent you?
Your last message was "is the sky blue?"

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could easily put this on a home server and have it available across all of our machines without any extra setup if we really wanted to&lt;/p&gt;

&lt;p&gt;Of course, this is still super barebones and in reality, we’d do a few more things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Supervisor to handle crashes&lt;/li&gt;
&lt;li&gt;Changing &lt;code&gt;:gptserver&lt;/code&gt; to be global, so we don’t need to know the hostname&lt;/li&gt;
&lt;li&gt;Some way to reset, or clear the message history to not have messages endlessly pile up&lt;/li&gt;
&lt;li&gt;Better error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;among others&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dvcrn/elixir-chatgpt"&gt;Check out the code for this on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>openai</category>
      <category>elixir</category>
      <category>genserver</category>
    </item>
    <item>
      <title>Refactoring code with ChatGPT</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/refactoring-code-with-chatgpt-1n9l</link>
      <guid>https://dev.to/dvcrn/refactoring-code-with-chatgpt-1n9l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4sct5eliqtphuhmwero.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4sct5eliqtphuhmwero.png" alt="robot-in-front-of-screen" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;From my blog: &lt;a href="https://david.coffee/refactoring-code-with-chat-gpt/" rel="noopener noreferrer"&gt;https://david.coffee/refactoring-code-with-chat-gpt/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT is really damn impressive and the more I read about it, the more it feels like withcraft. I see people using it for game development, coming up with titles, improving prompts for MidJourney and and and. It's shaping up to be one of those universally useful tools that are great to keep around.&lt;/p&gt;

&lt;p&gt;Here's one thing I had on my mind: Can it help me when I code? &lt;/p&gt;

&lt;p&gt;I was working on a little pet project and had some pretty inefficient code I slapped together for a middleware while watching TV:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ParseJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code takes whatever is passed into the request body, tries to JSON decode it, then puts the result into context. If no body exists or isn't JSON, it will pass &lt;code&gt;nil&lt;/code&gt; instead. &lt;/p&gt;

&lt;p&gt;I wanted to shorten and make it a bit more efficient but though it'd be a cool experiment to let ChatGPT do it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Can you refactor the following code to make it more efficient?  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;code from above here&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT: Here are a few ways the provided Go code can be imporved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;ParseJsonBody()&lt;/code&gt; function defines a &lt;code&gt;data&lt;/code&gt; variable to store the parsed JSON body, but it doesn't appear to be used anywhere in the function. This variable can be removed.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ParseJsonBody()&lt;/code&gt; function creates a new &lt;code&gt;context.Context&lt;/code&gt; with a key-value pair for the parsed JSON body, but it sets the value to &lt;code&gt;nil&lt;/code&gt; in both branches of the &lt;code&gt;if&lt;/code&gt; statement. This means that the value of the parsed JSON body will always be &lt;code&gt;nil&lt;/code&gt;, regardless of whether the JSON body was parsed successfully or not. The value of the parsed JSON body should be set to the parsed JSON data if parsing was successful, and &lt;code&gt;nil&lt;/code&gt; if parsing failed.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ParseJsonBody()&lt;/code&gt; function passes the original &lt;code&gt;http.Request&lt;/code&gt; object to the &lt;code&gt;next&lt;/code&gt; handler, even if the JSON body was parsed successfully. In this case, the &lt;code&gt;http.Request&lt;/code&gt; object should be updated with the new &lt;code&gt;context.Context&lt;/code&gt; that contains the parsed JSON body before being passed to the &lt;code&gt;next&lt;/code&gt; handler.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is how the code can be improved:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt;
  &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
  &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ParseJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Huh, turns out I copy pasted and forgot to change the normal branch to actually add &lt;code&gt;data&lt;/code&gt; into the context - whoops. ChatGPT spotted this, understood what this was supposed to do and fixed it.&lt;/p&gt;

&lt;p&gt;Can we make it more compact though? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Can you make the code more compact and more DRY?&lt;/p&gt;

&lt;p&gt;ChatGPT: Yes, the provided code can be made more compact and DRY (Don't Repeat Yourself) by removing the duplicate code and using a single &lt;code&gt;context.Context&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;Here is how the code can be improved:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt;
  &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
  &lt;span class="s"&gt;"net/http"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ParseJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;This is exactly what I had in mind for refactoring this code: No need for the if condition at all because &lt;code&gt;var data map[string]interface{}&lt;/code&gt;  is &lt;code&gt;nil&lt;/code&gt; by default. Only nitpick would be that we assign &lt;code&gt;err&lt;/code&gt; and never use it, so the compiler will complain about this code. &lt;/p&gt;

&lt;p&gt;Let's add a function to get the data out of the context again. Can ChatGPT summon this function for us? &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Please add a function that takes the body out of the context again &lt;/p&gt;

&lt;p&gt;ChatGPT: To extract the parsed JSON body from the &lt;code&gt;context.Context&lt;/code&gt; object, a new function can be added to the &lt;code&gt;middleware&lt;/code&gt; package. This function can take a &lt;code&gt;context.Context&lt;/code&gt; object as an argument and return the parsed JSON body, if it exists in the context, or &lt;code&gt;nil&lt;/code&gt; otherwise.&lt;/p&gt;

&lt;p&gt;Here is an example implementation of this function:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt;
  &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ParseJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
      &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;GetParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;To use this function, you can pass a &lt;code&gt;context.Context&lt;/code&gt; object as an argument and check the return value for &lt;code&gt;nil&lt;/code&gt; to determine whether the JSON body was parsed successfully or not.&lt;/p&gt;

&lt;p&gt;Here is an example of how this function can be used:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"context"&lt;/span&gt;
  &lt;span class="s"&gt;"fmt"&lt;/span&gt;
  &lt;span class="s"&gt;"net/http"&lt;/span&gt;

  &lt;span class="s"&gt;"github.com/myproject/middleware"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;parsedBody&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsedBody&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JSON body was not parsed"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Short and concise. It understood what I wanted it to do, it delivered a solution and even gave an explanation with examples how to use it!&lt;/p&gt;

&lt;p&gt;After a bit of hacking I noticed that using &lt;code&gt;map[string]interface{}&lt;/code&gt; is kind of a PITA because we have to do many type assertions and checks if keys exist. I got curious if we can tell it to swap out the type into something custom that has a nicer interface:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Instead of using map[string]interface{}, let's use a custom type that has methods for getting the value for a given key, or a default&lt;/p&gt;

&lt;p&gt;ChatGPT: To use a custom type that has methods for getting the value for a given key or a default value, you can define a new &lt;code&gt;ParsedJsonBody&lt;/code&gt; type that satisfies the &lt;code&gt;context.Context&lt;/code&gt; interface. This type can be a struct that contains a &lt;code&gt;map[string]interface{}&lt;/code&gt; field to store the parsed JSON body.&lt;/p&gt;

&lt;p&gt;Here is an example implementation of the &lt;code&gt;ParsedJsonBody&lt;/code&gt; type:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The &lt;code&gt;ParseJsonBody()&lt;/code&gt; middleware function can be updated to use the &lt;code&gt;ParsedJsonBody&lt;/code&gt; type instead of &lt;code&gt;map[string]interface{}&lt;/code&gt;:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hmm okay, this is where things get a bit weird, but that could have also been my way of explaining what I wanted.&lt;/p&gt;

&lt;p&gt;The code &lt;em&gt;looks ok&lt;/em&gt; on first skimming, but it ignored my instruction to have a default. I think it interpreted that as "we'll return a default", but not one I can specify. &lt;/p&gt;

&lt;p&gt;There's lots of type assertions in there that don't have any checks, so this will panic if we were to try to &lt;code&gt;GetInt&lt;/code&gt; something that isn't an int. Also, &lt;em&gt;you can define a new &lt;code&gt;ParsedJsonBody&lt;/code&gt; type that satisfies the &lt;code&gt;context.Context&lt;/code&gt; interface&lt;/em&gt; is nonsense, and so is the &lt;code&gt;Value()&lt;/code&gt; method. &lt;/p&gt;

&lt;p&gt;What I actually wanted was a custom type like &lt;code&gt;type MyType map[string]interface{}&lt;/code&gt;, not a struct with a type field, but whatever.&lt;/p&gt;

&lt;p&gt;Let's ask directly if it thinks anything is wrong with that code:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Thanks for the code, do you see any problems with this implementation?&lt;/p&gt;

&lt;p&gt;ChatGPT: There is a potential problem with the provided implementation of the &lt;code&gt;ParsedJsonBody&lt;/code&gt; type. The &lt;code&gt;Value()&lt;/code&gt; method returns a pointer to the &lt;code&gt;ParsedJsonBody&lt;/code&gt; struct, but the &lt;code&gt;Get()&lt;/code&gt;, &lt;code&gt;GetString()&lt;/code&gt;, and &lt;code&gt;GetInt()&lt;/code&gt; methods return values of the underlying &lt;code&gt;data&lt;/code&gt; field. This means that if the &lt;code&gt;ParsedJsonBody&lt;/code&gt; struct is modified after it is added to the &lt;code&gt;context.Context&lt;/code&gt; object, the modifications will be reflected in the &lt;code&gt;context.Context&lt;/code&gt;, but not in the return values of the &lt;code&gt;Get()&lt;/code&gt;, &lt;code&gt;GetString()&lt;/code&gt;, and &lt;code&gt;GetInt()&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;To fix this problem, the &lt;code&gt;Get()&lt;/code&gt;, &lt;code&gt;GetString()&lt;/code&gt;, and &lt;code&gt;GetInt()&lt;/code&gt; methods should return copies of the values in the &lt;code&gt;data&lt;/code&gt; field, rather than the values themselves.&lt;/p&gt;

&lt;p&gt;Here is an updated implementation of the &lt;code&gt;ParsedJsonBody&lt;/code&gt; type that fixes this problem:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;parsedJsonBodyContextKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;The good: It fixed the type assertions and now has a checks, neat!&lt;/p&gt;

&lt;p&gt;The bad: The explanation it gave on why it added those doesn't make sense. It didn't actually change those to &lt;em&gt;return copies of values&lt;/em&gt;. Let's do one more optimization pass.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Please further optimize this code&lt;/p&gt;

&lt;p&gt;ChatGPT: Here are a few ways the provided implementation of the &lt;code&gt;ParsedJsonBody&lt;/code&gt; type can be further optimized:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Value()&lt;/code&gt; method currently returns &lt;code&gt;p&lt;/code&gt; if the &lt;code&gt;key&lt;/code&gt; argument matches &lt;code&gt;parsedJsonBodyContextKey&lt;/code&gt;, and &lt;code&gt;nil&lt;/code&gt; otherwise. Since &lt;code&gt;parsedJsonBodyContextKey&lt;/code&gt; is a global variable that is only used in the &lt;code&gt;ParseJsonBody()&lt;/code&gt; middleware function, this check is unnecessary. The &lt;code&gt;Value()&lt;/code&gt; method can simply return &lt;code&gt;p&lt;/code&gt; without checking the &lt;code&gt;key&lt;/code&gt; argument.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Get()&lt;/code&gt;, &lt;code&gt;GetString()&lt;/code&gt;, and &lt;code&gt;GetInt()&lt;/code&gt; methods currently check if the specified key exists in the &lt;code&gt;data&lt;/code&gt; field and return a default value if the key does not exist. This check can be removed, because the &lt;code&gt;data&lt;/code&gt; field is a &lt;code&gt;map[string]interface{}&lt;/code&gt; and trying to access a non-existent key in a map will return the zero value for the type of the map's value (&lt;code&gt;interface{}&lt;/code&gt; in this case, which is &lt;code&gt;nil&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;GetString()&lt;/code&gt; and &lt;code&gt;GetInt()&lt;/code&gt; methods currently use the &lt;code&gt;ok&lt;/code&gt; return value of the type assertion to determine whether the value for the specified key is of the expected type. This check can be removed, because&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Me: Please show me an implementation with those points&lt;/p&gt;

&lt;p&gt;ChatGPT: &lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// The key argument is not used, so we can simply return p&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// We don't need to check if the key exists, because trying&lt;/span&gt;
  &lt;span class="c"&gt;// to access a non-existent key in a map will return the zero&lt;/span&gt;
  &lt;span class="c"&gt;// value for the type of the map's value (nil in this case)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// We don't need to check the type of the value, because trying&lt;/span&gt;
  &lt;span class="c"&gt;// to assert a non-string value to a string type will return&lt;/span&gt;
  &lt;span class="c"&gt;// the zero value for the string type ("" in this case)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ParsedJsonBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;GetInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// We don't need to check the type of the value, because trying&lt;/span&gt;
  &lt;span class="c"&gt;// to assert a non-int value to an int type will return the&lt;/span&gt;
  &lt;span class="c"&gt;// zero value for the int type (0 in this case)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;It got more compact for sure, but the code is wrong. It got rid of the type checks with the confident explanation that type-asserting &lt;em&gt;a non-int value to an int type will return the zero value for the int type&lt;/em&gt;, but this is not correct and will panic. Here's the code in Golang playground to try out: &lt;a href="https://play.golang.com/p/8jjBJyrJ1uC" rel="noopener noreferrer"&gt;https://play.golang.com/p/8jjBJyrJ1uC&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While impressive, at this point it's internal state of my program and what I'm trying to do is a bit too messed up, and the confidently wrong explanation+implementation can be dangerous. For a moment I self-doubted my own knowledge of Golang and had to try this out in the playground to make sure. &lt;/p&gt;

&lt;p&gt;The network isn't specifically trained on code, so I guess it just doesn't fully understand the inner-workings of Golang, without providing enough context to it first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating unit tests
&lt;/h2&gt;

&lt;p&gt;Let's do something fun - unit tests (&lt;em&gt;groans&lt;/em&gt;)! If it can spit out functional unit tests for stuff I'm too lazy to write, that would already be very helpful. After all, it seems to understand code and is able to give explanations for what's going on.&lt;/p&gt;

&lt;p&gt;The code here is a snippet from a JWT token parser. It validates the token, extracts sid/uid scopes (UUID) and checks if those are actually valid UUIDs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Me: Consider the following Golang code:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// other code here&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// parse sid&lt;/span&gt;
  &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;// parse uid&lt;/span&gt;
  &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;uidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;SessionID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;uidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// other code here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Please generate some testcases for when sid or uid are invalid&lt;/p&gt;

&lt;p&gt;ChatGPT: Here are some test cases for when the &lt;code&gt;sid&lt;/code&gt; or &lt;code&gt;uid&lt;/code&gt; values in the token are invalid:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestValidateTokenInvalidSID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// Create a new client&lt;/span&gt;
 &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-secret-key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Create a test token&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
 &lt;span class="c"&gt;// Set the sid value to something invalid&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid-sid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpirationKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

 &lt;span class="c"&gt;// Sign the token&lt;/span&gt;
 &lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hmacSecret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error signing token: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c"&gt;// Validate the token&lt;/span&gt;
 &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected error to be %s, got %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestValidateTokenInvalidUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// Create a new client&lt;/span&gt;
 &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-secret-key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="c"&gt;// Create a test token&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="c"&gt;// Set the uid value to something invalid&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid-uid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
 &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpirationKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unix&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

 &lt;span class="c"&gt;// Sign the token&lt;/span&gt;
 &lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hmacSecret&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unexpected error signing token: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c"&gt;// Validate the token&lt;/span&gt;
 &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signedToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected error to be %s, got %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Sweet as. Sadly it's missing a bit of context here too, so I can't copy-paste the test as is. &lt;code&gt;jwt.Sign()&lt;/code&gt; isn't returning a &lt;code&gt;jwt.Token&lt;/code&gt;, but the jwt token in signed form, so it can't get passed to &lt;code&gt;validateToken&lt;/code&gt;, but there's no way that it could have known that, without feeding the the jwt package into it first. Besides that it looks good, and the tests run after fixing that!&lt;/p&gt;

&lt;p&gt;And to go full circle, now that we have more unit tests, let's ask it to optimize my &lt;code&gt;validateToken&lt;/code&gt; function as well:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One possible optimization for the &lt;code&gt;validateToken&lt;/code&gt; function would be to move the code that parses the &lt;code&gt;sid&lt;/code&gt; and &lt;code&gt;uid&lt;/code&gt; values into a separate function, and then call that function from &lt;code&gt;validateToken&lt;/code&gt; instead of duplicating the code. This would make the code easier to read and maintain, and would also avoid repeating the same error-checking code multiple times.&lt;/p&gt;

&lt;p&gt;Here is an example of how the optimized &lt;code&gt;validateToken&lt;/code&gt; function could look like:&lt;/p&gt;


&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;validateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// Validate the token&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c"&gt;// Parse the sid and uid values from the token&lt;/span&gt;
 &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseSID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;SessionID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;parseSID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// parse sid&lt;/span&gt;
 &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;sidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidSID&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;parseUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;// parse uid&lt;/span&gt;
 &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;uidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrTokenInvalidUID&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;uidUUID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;I was hoping it'd catch the naked &lt;code&gt;.(string)&lt;/code&gt; type assertion, but code splitting is also nice :D &lt;/p&gt;

&lt;p&gt;Although this iteration doesn't really &lt;em&gt;avoid repeating the same error-checking code multiple times&lt;/em&gt; like it claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  Collaborative hacking with an AI buddy
&lt;/h2&gt;

&lt;p&gt;I use Copilot quite a lot so I'm already used to having an AI pair-programming buddy, but I strongly believe that as these tools get smarter, they will drastically change the way how we as engineers work. &lt;/p&gt;

&lt;p&gt;While not there yet, AI will be able to generate more efficient code &lt;em&gt;faster than us&lt;/em&gt;, spot errors we'd miss, or flat-out generate entire applications based on spec input.&lt;/p&gt;

&lt;p&gt;When Stable Diffusion came out, some people integrated it into their photo editors to quickly generate content based on a prompt. I am sure we'll see something like this, but for code, in no time. Imagine reading through some code, hitting an "AI" button, then ask it to refactor a routine, or give an explanation what that code does. The plugin will then send all the necessary context to the neural net and ask it to do the task.&lt;/p&gt;

&lt;p&gt;The next generation of AI powered plugins could also do things like quickly provide feedback when it strongly believes a mistake has been made, or when it has a high confidence score for a completion / refactoring.&lt;/p&gt;

&lt;p&gt;I think that would be really dang cool!&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>ai</category>
      <category>go</category>
    </item>
    <item>
      <title>Syncing app configurations across machines with unisync</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Sat, 19 Nov 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/syncing-app-configurations-across-machines-with-unisync-eaf</link>
      <guid>https://dev.to/dvcrn/syncing-app-configurations-across-machines-with-unisync-eaf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7eyuyt6zds76on5ykgx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7eyuyt6zds76on5ykgx.jpg" alt="unisync" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; : &lt;a href="https://github.com/dvcrn/unisync" rel="noopener noreferrer"&gt;unisync&lt;/a&gt; is a little tool I wrote that diffs and syncs your application settings across Macs, powered by unison&lt;/p&gt;

&lt;p&gt;Config management across machines is kind of a PITA. Some apps luckily natively support syncing settings, but most don’t.&lt;/p&gt;

&lt;p&gt;There are some solutions like the excellent &lt;a href="https://github.com/lra/mackup" rel="noopener noreferrer"&gt;Mackup&lt;/a&gt; that try to fix this issue by moving all config files into a shared folder (like Dropbox), then creating symlinks from the application settings folders (eg &lt;code&gt;~/Library/Preferences/xxxx.plist&lt;/code&gt;) into that Dropbox folder.&lt;/p&gt;

&lt;p&gt;I used mackup for a while (even with my own &lt;a href="https://github.com/dvcrn/dotfiles/tree/master/mackup" rel="noopener noreferrer"&gt;config files&lt;/a&gt;), but eventually got frustrated when symlinks randomly broke, for example, because the app decided it wanted to write the full file fresh to disk, effectively overwriting the symlink. Then some other apps detect symlinks and refuse to read them.&lt;/p&gt;

&lt;p&gt;I tried to mitigate that by &lt;a href="https://github.com/lra/mackup/pull/1779" rel="noopener noreferrer"&gt;creating a PR to mackup&lt;/a&gt; that adds support for hardlinks, but tl;dr: hardlinks in APFS are not really hardlinks, and this is even more fragile with cloud storage than using symlinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unison is great
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bcpierce00/unison" rel="noopener noreferrer"&gt;unison&lt;/a&gt; is one of these really old tools that’s been around and developed forever. It’s a&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*“Unison is a file-synchronization tool for POSIX-compliant systems (e.g. &lt;em&gt;BSD and GNU/Linux), macOS and Windows, with the caveat that the platform must be supported by OCaml”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;basically, it syncs files, kind of like a bi-directional rsync: Where rsync syncs one way (and alternatively deletes files), unison goes both ways and finds files that changed, got added or deleted across multiple directory nodes.&lt;/p&gt;

&lt;p&gt;Bi-directional sync makes it great for those pesky config dirs that could change on one host without us noticing. By diffing the directories against, say, a directory in Dropbox, unison can determine what actually changes, and copies the files accordingly.&lt;/p&gt;

&lt;p&gt;If a file is updated on Host B and is then synced through Dropbox to Host A, unison on Host A would see that the file in Dropbox is now newer than on the local one, and overwrite it with the version from Dropbox.&lt;/p&gt;

&lt;p&gt;unison can even sync &lt;em&gt;across&lt;/em&gt; machines directly!&lt;/p&gt;

&lt;h2&gt;
  
  
  Mackup + Unison = Unisync
&lt;/h2&gt;

&lt;p&gt;That brings us to &lt;a href="https://github.com/dvcrn/unisync" rel="noopener noreferrer"&gt;unisync&lt;/a&gt;. Unisync is using the Mackup idea of having a library of app configurations that state where config files are, but instead of using symlinks, it calls into &lt;code&gt;unison&lt;/code&gt; to do the actual copying of files. So in theory, if files changed on one machine and then get synced to other machines, those files would get marked as changed / new, and get copied over the existing local file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxow5ckw4w6t7rjfwpml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbxow5ckw4w6t7rjfwpml.png" alt="Unisync screenshot" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because unisync is using unison under the hood, we can do more fancy things like providing a tree with files we want to handle, but add regex or naming patterns to ignore rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Raycast
friendlyName: raycast
files:
  - basePath: ~/Library/Preferences/
    includedFiles:
      - com.raycast.macos.plist
    ignoredFiles:
      - Name somethingToIgnore.plist

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unisync also supports the &lt;a href="https://github.com/dvcrn/unisync/tree/main/apps/mackup" rel="noopener noreferrer"&gt;entire Mackup app library&lt;/a&gt; for syncing to get you started, and comes with LaunchAgents that make it run automatically in the background.&lt;/p&gt;

&lt;p&gt;Check it out on GitHub and let me know if this tool is useful for you: &lt;a href="https://github.com/dvcrn/unisync" rel="noopener noreferrer"&gt;https://github.com/dvcrn/unisync&lt;/a&gt;&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Why and how I use k8s for my personal stuff (and love it)</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/why-and-how-i-use-k8s-for-my-personal-stuff-and-love-it-50hj</link>
      <guid>https://dev.to/dvcrn/why-and-how-i-use-k8s-for-my-personal-stuff-and-love-it-50hj</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fwhale-through-space.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fwhale-through-space.png" alt="whale-through-space"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First published &lt;a href="https://david.coffee/why-and-how-i-use-k8s-for-personal-stuff/" rel="noopener noreferrer"&gt;on my personal blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’m one of those odd nuts that love kubernetes. I like it so much that it’s currently powering a lot of my personal stuff and hobby projects. So instead of the usual “k8s is bloated”, “k8s is overkill” or “why you don’t need k8s” posts, today let’s talk about why k8s is actually great for personal stuff, and why you should maybe also consider using it? :)&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why I love kubernetes for personal stuff
&lt;/h2&gt;

&lt;p&gt;Let’s start with the &lt;em&gt;why&lt;/em&gt;. Why would I use something as bloated and heavy as kubernetes for personal stuff, isn’t it a huge overkill? I could go on and on about why it’s great, but let’s take a look at the big reasons.&lt;/p&gt;

&lt;p&gt;I want to also preface that small managed clusters can be really cheap. My main managed “cluster” (putting it in quotes because it only has 1 node) on &lt;a href="https://www.digitalocean.com/products/kubernetes" rel="noopener noreferrer"&gt;digitalocean&lt;/a&gt; costs $12/month. Compare that with other hosting providers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infra as code 👨‍💻
&lt;/h3&gt;

&lt;p&gt;k8s, same as with &lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;terraform&lt;/a&gt;, allows me to specify my infrastructure as code. I write &lt;em&gt;manifest&lt;/em&gt; files that specify how stuff is going to get run. If I want to make a change to my infra, all I need to do is update those manifests, apply them, and I’m done. Look at this snippet from a manifest that tells k8s that I want to run a webdav container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      containers:
        - name: webdav
          image: bytemark/webdav
          ports:
            - containerPort: 80
          env:
            - name: "USERNAME"
                value: "myuser"
            - name: "PASSWORD"
              valueFrom:
                secretKeyRef:
                  name: webdav-credentials
                  key: password

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because I have everything in code, I can set up my entire cluster from scratch on any provider I want. If I no longer like digitalocean, all I have to do is create a cluster somewhere else and do a &lt;code&gt;kubectl apply&lt;/code&gt;. The new setup will be identical (or almost identical) to my previous one, it’s almost provider agnostic (there are some provider-specific things like persistent volumes, but we’ll get to those later).&lt;/p&gt;

&lt;p&gt;Manifests are also idempotent, I can re-apply the same files a dozen times without worry of breaking anything. If resources already exist, they stay existent and if there are no changes, the manifest will just get ignored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extremely easy to add storage or IP addresses 🔄
&lt;/h3&gt;

&lt;p&gt;Managed kubernetes clusters (like the digitalocean one) are integrated with the entire cloud platform of that provider, so it’s &lt;em&gt;very&lt;/em&gt; easy to do things like adding block storage or loadbalancers. How easy? Check this out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: some-name
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: do-block-storage

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manifest when applied will tell DigitalOcean (through the kubernetes controller) to provision a 1Gb block storage volume. If that 1Gb fills up at some point, I change it to 10Gb, re-apply and DigitalOcean will automatically resize the volume to 10GB for me - neat!&lt;/p&gt;

&lt;p&gt;Even cooler, I can now use this storage volume on any of my containers that are running inside my cluster. All I need to do is add a mount point into the manifest, and suddenly ephemeral containers have a way to write persistent data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      volumes:
        - name: some-name-volume
          persistentVolumeClaim:
            claimName: some-name
            readOnly: false

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I want to get rid of the volume, I do &lt;code&gt;kubectl delete pvc some-name&lt;/code&gt; and it gets wiped. (There is a &lt;em&gt;retain&lt;/em&gt; setting if you don’t want volume content to get deleted)&lt;/p&gt;

&lt;p&gt;This isn’t just storage though, the same thing happens with stuff like &lt;em&gt;LoadBalancers&lt;/em&gt; and egress IPs. Provisioning resources on kubernetes causes DigitalOcean to get a static IP + LoadBalancer ready for us, wait until it’s good to go, then assign it to the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  It makes running stuff and keeping stuff running a breeze 🍃
&lt;/h3&gt;

&lt;p&gt;K8s is very battle-tested. A lot of companies run on kubernetes so it’s grown to be robust and fault-tolerant. And if something does end up not working or acting up, I can be sure that I will find the answer to my issue in no time.&lt;/p&gt;

&lt;p&gt;Besides running containers, k8s does things like keeping those running (if you want that). Let’s take a look at the webdav example from above, but conjure up some more yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: webdav
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webdav
  template:
    metadata:
      labels:
        app: webdav
    spec:
      containers:
        - name: webdav
          image: bytemark/webdav
          ports:
            - containerPort: 80
          env:
            - name: "USERNAME"
                value: "myuser"
            - name: "PASSWORD"
              valueFrom:
                secretKeyRef:
                  name: webdav-credentials
                  key: password

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webdav container is now in a &lt;em&gt;Deployment&lt;/em&gt; with 1 replica. This means k8s will make sure there is &lt;em&gt;always&lt;/em&gt; 1 webdav container running. You can even &lt;code&gt;kubectl delete pod &amp;lt;webdav-xxxx&amp;gt;&lt;/code&gt; to destroy the webdav container, and kubernetes will just respawn a new one.&lt;/p&gt;

&lt;p&gt;My server can crash, the container can crash - doesn’t matter, don’t care. In the end, kubernetes will jump in and just scale a new container up again.&lt;/p&gt;

&lt;p&gt;And if I want more containers? All I have to do is change &lt;code&gt;replicas: 1&lt;/code&gt; to &lt;code&gt;replicas: 2&lt;/code&gt;, and now I will always have 2 webdav containers. This makes scaling things up and down as easy as changing a yaml file and applying it.&lt;/p&gt;

&lt;p&gt;But there are other cool things that &lt;em&gt;just work&lt;/em&gt; like CronJobs! I can specify my CronJob yaml manifests, tell kubernetes when it should run those, and I can be sure they are getting run. No messing with crontab or some cloud scheduling thing, I can do it right from within k8s, together with the rest of my infra.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy scaling when it becomes necessary 📈
&lt;/h3&gt;

&lt;p&gt;So we saw in the previous section that we can scale containers however we please. It gets even cooler if we deal with multiple nodes on the cluster. Say my hobby project is getting more popular - instead of a simple web server I had to add a database and maybe a queue server. Resources are getting tight, and stuff doesn’t run as nicely any more.&lt;/p&gt;

&lt;p&gt;What I can do is, I can open up the DigitalOcean kubernetes admin, go to my node settings, and either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase the nodes of the cluster to 2&lt;/li&gt;
&lt;li&gt;Destroy the current 1-node setup and replace it with a stronger node&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because k8s is managing the cluster, even if we destroy the node and add a new, stronger node in its place, it will still do exactly what it did before: Make sure those containers are running in the configuration we specified as soon as the new machine is up and running.&lt;/p&gt;

&lt;p&gt;If we increase the cluster size from 1 to 2, kubernetes will look at available resources on the cluster, see that a new node is available, and balance those containers for us, by maybe moving the queue server to the new machine, or having one of those 2 webdav replicas there instead. (Of course, if we want less magic we can also tell it specifically what it should do and how it should utilize those nodes, like always having one replica on each node)&lt;/p&gt;

&lt;p&gt;It goes the other way as well: We’re getting tight on money so we decided to cut down on resources. Removing a node from the cluster will make k8s reshuffle our containers and consolidate them onto the remaining nodes, and THEN shut the node down, without downtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How I use kubernetes for my personal stuff
&lt;/h2&gt;

&lt;p&gt;Now that you’ve heard the main reasons why I like kubernetes, let’s take a look at how I specifically am using it on a daily basis.&lt;/p&gt;

&lt;p&gt;Let’s start with the provider - I mentioned that I use DigitalOcean kubernetes for my cloud of choice. I like them because the cost is predictable and the managed cluster is cheap. I am always sure how my bill will look like and there are no surprises. At $12/node for kubernetes I really have no complaints here. It’s great! This price doesn’t include a LoadBalancer/static IP but for most stuff I don’t really need those.&lt;/p&gt;

&lt;p&gt;k8s can do a lot, but the parts that I actually use are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployments: Fancy way of saying “keep this many replicas running at all time”&lt;/li&gt;
&lt;li&gt;Services: Fancy way of saying “I need an cluster internal/external IP for these containers”&lt;/li&gt;
&lt;li&gt;Ingress: A fancy router like “route domain david.coffee to this pod”&lt;/li&gt;
&lt;li&gt;Cron&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exposing stuff for personal use only with tailscale
&lt;/h3&gt;

&lt;p&gt;Most of my stuff doesn’t need to be public. It’s either a cronjob that does something and doesn’t need inbound connections, or it’s stuff that’s only public to me.&lt;/p&gt;

&lt;p&gt;To make things public to myself only, I am using &lt;a href="https://tailscale.com" rel="noopener noreferrer"&gt;tailscale&lt;/a&gt; as my overlay network of choice (other great options are ZeroTier and Slack Nebula). Tailscale utilizes wireguard tunnels to build a Layer 3 p2p network that most of the times &lt;em&gt;Just Works&lt;/em&gt;™️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fmesh-network.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fmesh-network.svg" alt="mesh-network"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cool thing is, I can embed the sidecar container into any kubernetes pod I want to access, and it becomes available to my tailscale network. All I have to do is add some yaml to a pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      containers:
        - name: webdav
          image: bytemark/webdav
          ports:
            - containerPort: 80
          env:
            - name: "USERNAME"
                value: "myuser"
            - name: "PASSWORD"
              valueFrom:
                secretKeyRef:
                  name: webdav-credentials
                  key: password
        - name: ts-sidecar
          imagePullPolicy: Always
          image: "ghcr.io/tailscale/tailscale:latest"
          env:
            - name: TS_KUBE_SECRET
              value: "tailscale-webdav"
            - name: TS_USERSPACE
              value: "false"
            - name: TS_AUTH_KEY
              valueFrom:
                secretKeyRef:
                  name: tailscale-auth
                  key: TS_AUTH_KEY
          securityContext:
            capabilities:
              add:
                - NET_ADMIN

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… and when my connection is established, I can just do &lt;code&gt;http://webdav-tailscale:80&lt;/code&gt; and I have a fully personal connection to my webdav container. No ingress or load-balancer needed ✌️&lt;/p&gt;

&lt;p&gt;A non-tailscale option would be to use port-forwarding into the pod directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  # Listen on port 8888 locally, forwarding to 80 in the pod
  kubectl port-forward pod/mypod 8888:80

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exposing public stuff without static IP + LoadBalancer with Nginx (the frugal option)
&lt;/h3&gt;

&lt;p&gt;Because my cluster has only 1 node, I almost never need a proper loadbalancer or static IP.&lt;/p&gt;

&lt;p&gt;My ingress controller of choice is &lt;a href="https://github.com/kubernetes/ingress-nginx" rel="noopener noreferrer"&gt;nginx&lt;/a&gt;, and we can tweak it to run off a ClusterIP service instead of a full blown load balancer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install ingress-nginx ingress-nginx/ingress-nginx -f nginx-ingress.yml


---
controller:
  kind: DaemonSet
  daemonset:
    useHostPort: true
  dnsPolicy: ClusterFirstWithHostNet
  hostNetwork: true
  service:
    type: ClusterIP
  resources:
    requests:
      cpu: 10m
rbac:
  create: true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;DaemonSet&lt;/code&gt; is something we haven’t talked about yet, it’s similar to a Deployment with the exception that it makes sure the &lt;em&gt;thing&lt;/em&gt; is running on all nodes of the cluster. So if the cluster has 1 node, there’ll be one of those. If there are 3 nodes, we’ll have 3 nginx controllers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hostNetwork: true&lt;/code&gt; is running that service off the host directly, so we can hook up &lt;code&gt;&amp;lt;machineip&amp;gt;:80&lt;/code&gt; and let nginx handle the routing.&lt;/p&gt;

&lt;p&gt;Then once we have that contorller running we can add actual Ingress resources to do something based on domain name like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: ingress-nginx-controller
      port:
        number: 80
  rules:
    - host: somethingsomething.david.coffee
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: somethingsomething-service
                port:
                  number: 80

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make sure the IP stays up to date, we can hook up a cronjob and update the cloudflare DNS records automatically, for maximum money saving.&lt;/p&gt;

&lt;p&gt;Of course if you have the cash and want to scale up, just get a proper static IP with &lt;code&gt;LoadBalancer&lt;/code&gt;. It’s only $10 extra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy whale-ing
&lt;/h2&gt;

&lt;p&gt;As you can tell by this long post, I am a fan of kubernetes and use it for my personal stuff &lt;em&gt;a lot&lt;/em&gt;. If you read until this point, congratulations! You made it. Let me know &lt;a href="https://twitter.com/dvcrn" rel="noopener noreferrer"&gt;on twitter&lt;/a&gt; if you think this kind of post is useful, or if there are parts you’d like to know more about.&lt;/p&gt;

&lt;p&gt;Happy whale-ing! 🐳&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fwhale-space-stars.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavid.coffee%2Fassets%2F2022-11-why-i-like-k8s%2Fwhale-space-stars.png" alt="whale-space-stars"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k8s</category>
    </item>
    <item>
      <title>Venturing into WASM on React Native and Golang</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Sat, 15 Oct 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/venturing-into-wasm-on-react-native-and-golang-21ld</link>
      <guid>https://dev.to/dvcrn/venturing-into-wasm-on-react-native-and-golang-21ld</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SK0axh5t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/goling.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SK0axh5t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/goling.png" alt="Gopher Programming" width="880" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Appeared first on my &lt;a href="https://david.coffee/react-native-wasm-golang/"&gt;personal blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently had the idea to create a little react native app for FastMails MaskedEmails, to quickly add new ones on my device, so I don’t have to open the FastMail app all the time.&lt;/p&gt;

&lt;p&gt;I wanted to do this with as little code duplication as possible, so instead of rewriting a client from scratch, I decided to re-use my &lt;a href="https://github.com/dvcrn/maskedemail-cli"&gt;maskedemail-cli&lt;/a&gt; somehow. I could have probably compiled it down to a shared library and pulled it into Swift, but I am no iOS developer and know React already, so why not do it in React Native with WASM?&lt;/p&gt;

&lt;p&gt;I’ve never actually used wasm and was looking for an excuse to play around with it. Here’s what I learned.&lt;/p&gt;

&lt;p&gt;You can find the app I worked on during this writeup at &lt;a href="https://maskedemailmanager.david.coffee"&gt;https://maskedemailmanager.david.coffee&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Spoiler: You can’t use native WASM within JSC on React Native yet)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Golang and WASM, how does it actually work?
&lt;/h2&gt;

&lt;p&gt;Golang supports &lt;a href="https://github.com/golang/go/wiki/WebAssembly#webassembly"&gt;compiling to wasm&lt;/a&gt; since version 1.11, with further improvements happening in 1.13. The basic gist of making it spit out wasm is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOS=js GOARCH=wasm go build -o main.wasm

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Main interactions from Golang with JavaScript-land happen through the &lt;code&gt;js/syscall&lt;/code&gt; &lt;a href="https://pkg.go.dev/syscall/js"&gt;package&lt;/a&gt;. For example, if we want to create a js function, we can do it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cb = js.FuncOf(func(this js.Value, args []js.Value) any {
    fmt.Println("Hello World")
    return nil
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first argument is always &lt;code&gt;this&lt;/code&gt;, and the second argument is always an array of actual arguments passed to the function.&lt;/p&gt;

&lt;p&gt;We can also use &lt;code&gt;js.Global()&lt;/code&gt; to get a ref to the global object and call into js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;js.Global().Call("alert", "Hello from wasm!")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… and of course, we can also make our functions available on the global object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;js.Global().Set("myCoolFunc", cb)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Golang WASM from JavaScript
&lt;/h3&gt;

&lt;p&gt;Golang comes with some &lt;a href="https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js"&gt;glue code&lt;/a&gt; that you need to run wasm files built by Golang properly, that’s this copy statement you see in most guides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides setting up the environment, this code defines the &lt;code&gt;globalThis.Go&lt;/code&gt; &lt;a href="https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js#L95"&gt;object&lt;/a&gt; that we’ll use to run our wasm code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const go = new Go();
const res = await WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject);
go.run(res.instance);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(instantiateStreaming supports fetch; that’s why everything is so compact. we can also use &lt;code&gt;WebAssembly.instantiate(arrayBuffer, go.importObject)&lt;/code&gt; directly without fetch).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go.importObject&lt;/code&gt; provides values to be imported into the new instance, which defines &lt;a href="https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js#L209"&gt;all the supported syscalls&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing maskedemail-cli for WASM - async code and Promises
&lt;/h2&gt;

&lt;p&gt;First, we need to create glue code that exports the necessary functions from maskedemail-cli to JavaScript, in Golang.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func main() {
    done := make(chan struct{}, 0)
    js.Global().Set("maskedemailList", js.FuncOf(list))
    &amp;lt;-done
}

func list(this js.Value, args []js.Value) interface{} {
    // (...) get maskedemails here (...)
    return maskedEmailArray
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now here’s a problem: We can’t block the main goroutine with things like HTTP requests. It would be kinda weird if we do an ajax request in Golang and the browser freezes while our functions get executed…&lt;/p&gt;

&lt;p&gt;Instead, async code has to go into separate goroutines, which means in JavaScript… Promises!!&lt;/p&gt;

&lt;p&gt;Specifically, to make our code compatible with js promises from Golang, we have to use the Promise constructor through &lt;code&gt;js.Global()&lt;/code&gt;, effectively creating a Promise from scratch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func list(this js.Value, args []js.Value) interface{} {
    handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        resolve := args[0]
        reject := args[1]
        go func() {
            // (...) get maskedemails here (...)
            resolve.Invoke("Hi there")
        }
    })
    promise := js.Global().Get("Promise")
    return promise.New(handler)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;js.Global().Get("Promise")&lt;/code&gt; is getting the global Promise object, then calling &lt;code&gt;New&lt;/code&gt; onto it with a custom handler.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;main()&lt;/code&gt; from above would now populate &lt;code&gt;globalThis.list&lt;/code&gt;, and it’s signature would look like this: &lt;code&gt;list(): Promise&amp;lt;string&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a NPM package that’s using WASM
&lt;/h2&gt;

&lt;p&gt;Next, I wanted to publish an NPM package to pack away the complexity, but most guides on Golang + wasm were mainly focused on the browser, not NodeJS.&lt;/p&gt;

&lt;p&gt;Luckily the key steps are effectively the same:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the &lt;code&gt;wasm_exec.js&lt;/code&gt; glue script to populate globalThis.Go&lt;/li&gt;
&lt;li&gt;Load the wasm file through whatever method&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;WebAssembly.instantiate&lt;/code&gt; or &lt;code&gt;WebAssembly.instantiateStreaming&lt;/code&gt;, pass &lt;code&gt;go.importObject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;go.run(instance)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In NodeJS, we can solve 1. by doing a dirty &lt;code&gt;require("./wasm_exec.js")&lt;/code&gt;. The code runs fine in the nodejs runtime so no issues there. Too bad it’s polluting &lt;code&gt;globalThis&lt;/code&gt; and doesn’t give us a nice-to-use package back, but we can live with that.&lt;/p&gt;

&lt;p&gt;Point 2. is a tad trickier - we could of course, just load it with &lt;code&gt;fs.readFileSync&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const go = new globalThis.Go()
const buffer = fs.readFileSync('./main.wasm');
const inst = await WebAssembly.instantiate(buffer, go.importObject);
go.run(inst.instance);
// run func
globalThis.list();

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… but given that this is JavaScript, usage of &lt;code&gt;fs&lt;/code&gt; means that this is already unusable in a browser environment, which is not great.&lt;/p&gt;

&lt;p&gt;What’s more, &lt;code&gt;wasm_exec.js&lt;/code&gt; depends on a few browser-specific modules, mainly &lt;code&gt;crypto&lt;/code&gt; and &lt;code&gt;TextEncoder&lt;/code&gt;. So now we have some stuff that doesn’t work in the browser, and also some stuff that doesn’t work within node 😀 Let’s fix that with some polyfills:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!globalThis.crypto) {
  const crypto = require("crypto");
  globalThis.crypto = crypto;
}

if (!globalThis.TextEncoder) {
  const te = require("text-encoding-polyfill");
  globalThis.TextEncoder = te.TextEncoder;
  globalThis.TextDecoder = te.TextDecoder;
}

const go = new globalThis.Go()
const response = await fetch('./main.wasm');
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
const instance = await WebAssembly.instantiate(module);
go.run(inst.instance);
// run func 
globalThis.list()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of &lt;code&gt;fs&lt;/code&gt;, we can use &lt;code&gt;node-fetch&lt;/code&gt; (or the new NodeJS native one) and &lt;code&gt;fetch&lt;/code&gt; to pull the wasm file, then continue instantiating like usual. Now it’s no longer node/browser or fetches implementation dependent.&lt;/p&gt;

&lt;h4&gt;
  
  
  My final solution
&lt;/h4&gt;

&lt;p&gt;Because I wanted to package this for ESM and CJS simultaneously, I went with &lt;a href="https://rollupjs.org/guide/en/"&gt;rollup&lt;/a&gt; as bundler. &lt;a href="https://www.npmjs.com/package/@rollup/plugin-wasm"&gt;rollup-plugin-wasm&lt;/a&gt; can handle loading + instantiating of the actual wasm file similar to how we did above, so we can slim the code down like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// polyfills here 
require("./wasm_exec.js");
import wasmMain from "./static/main.wasm";

const go = new globalThis.Go();
const wasmObj = await wasmMain(go.importObject);
go.run(wasmObj.instance);
globalThis.list()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Packaging into a usable module
&lt;/h3&gt;

&lt;p&gt;To make everything a tad nicer, let’s export some functions that other modules can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface Mod {
  list: (token: string, accountId: string) =&amp;gt; Promise&amp;lt;MaskedEmail[]&amp;gt;;
}

const instantiatedPromise = new Promise&amp;lt;Mod&amp;gt;(async (resolve, reject) =&amp;gt; {
  const go = new (globalThis as any).Go();
  const wasmObj = await wasmMain(go.importObject);
  go.run(wasmObj.instance);

  resolve({
    list: (globalThis as any)["maskedemailList"],
  });
});

export const list = async (token: string, accountId: string) =&amp;gt; {
  const mod = await instantiatedPromise;
  return mod.list(token, accountId);
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have some waiting to do before wasm is usable, so we package all of that into a new &lt;code&gt;instantiatedPromise&lt;/code&gt;. The exported &lt;code&gt;list()&lt;/code&gt; is then awaiting that promise before doing anything, then executing &lt;code&gt;mod.list&lt;/code&gt;, our actual wasm function.&lt;/p&gt;

&lt;p&gt;To make the API nicer to use I’ve added typescript type definitions for the module, that’s what the &lt;code&gt;interface Mod&lt;/code&gt; is for. &lt;code&gt;instantiatedPromise&lt;/code&gt; will resolve with &lt;code&gt;Mod&lt;/code&gt;, so calling &lt;code&gt;mod.list&lt;/code&gt; will have the same return signature as &lt;code&gt;Mod.list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The package is available on &lt;a href="https://www.npmjs.com/package/maskedemail"&gt;npm&lt;/a&gt;, the code on &lt;a href="https://github.com/dvcrn/maskedemail-js"&gt;my github&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigating React Native + WASM
&lt;/h2&gt;

&lt;p&gt;By now, we have packaged everything so neatly that it doesn’t matter that we used Golang to create the wasm binary. It’s all just an npm package that &lt;em&gt;should&lt;/em&gt; be usable everywhere. (Ignore the big 7MB file size of the &lt;code&gt;.wasm&lt;/code&gt; file for now 😛)&lt;/p&gt;

&lt;p&gt;So all we have to do is &lt;code&gt;yarn add maskedemail&lt;/code&gt; and that should be it… right? Right??&lt;/p&gt;

&lt;p&gt;But life is not that simple, and adding that final lego piece was much harder than expected. Let’s go over what is making wasm + react native so hard?? (This was written by poking the simulator and debugging errors)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WebAssembly.instantiateStreaming&lt;/code&gt; / &lt;code&gt;WebAssembly.compileStreaming&lt;/code&gt; only works with &lt;code&gt;fetch&lt;/code&gt; if the server is returning &lt;code&gt;application/wasm&lt;/code&gt;, which react-native doesn’t. In fact, using &lt;code&gt;fetch&lt;/code&gt; to pull in resources the same way we do in NodeJS or the Browser is in RN flimsy at best

&lt;ul&gt;
&lt;li&gt;Expo has &lt;a href="https://docs.expo.dev/versions/latest/sdk/asset/#frommodulevirtualassetmodule"&gt;Asset.fromModule&lt;/a&gt; to make it possible to load arbitrary assets from disk&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WebAssembly.instantiateStreaming&lt;/code&gt; / &lt;code&gt;WebAssembly.compileStreaming&lt;/code&gt; doesn’t even exist in the react native JavaScriptCore runtime (&lt;code&gt;WebAssembly&lt;/code&gt; exists, but those functions don’t. Only &lt;code&gt;WebAssembly.instantiate&lt;/code&gt; and &lt;code&gt;WebAssembly.compile&lt;/code&gt; do, which work with ArrayBuffers&lt;/li&gt;
&lt;li&gt;Current asset loading functionality through &lt;code&gt;Asset.fromModule&lt;/code&gt; or fetch implementations for react native can pull assets, but rn’s &lt;code&gt;FileReader&lt;/code&gt; &lt;a href="https://github.com/facebook/react-native/blob/main/Libraries/Blob/FileReader.js#L77-L79"&gt;does not implement readAsArrayBuffer&lt;/a&gt;, so no &lt;code&gt;fetch().then((res) =&amp;gt; res.arrayBuffer())&lt;/code&gt;. No ArrayBuffer means no easy way to use &lt;code&gt;WebAssembly.instantiate&lt;/code&gt;, and also no easy trickery to convert Blob into an ArrayBuffer&lt;/li&gt;
&lt;li&gt;RN’s JavaScriptCore does not have a &lt;code&gt;crypto&lt;/code&gt; implementation&lt;/li&gt;
&lt;li&gt;Expos packager is trying to resolve all &lt;code&gt;require&lt;/code&gt; and build and doesn’t support dynamic imports, so having &lt;code&gt;require("crypto")&lt;/code&gt; in an if statement (like a env-dependent polyfill) will &lt;em&gt;still&lt;/em&gt; make the packager throw an error that the package doesn’t exist, effectively meaning that our generic solution ain’t gonna work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I probably forgot a few points but these were the most noticeable issues.&lt;/p&gt;

&lt;p&gt;I banged my head against &lt;code&gt;FileReader.readAsArrayBuffer&lt;/code&gt; to make it possible to read the &lt;code&gt;.wasm&lt;/code&gt; file from disk, tried a couple polyfills and re-implementations, but eventually gave up. There are &lt;code&gt;fs.readFile&lt;/code&gt; implementations that could work, but expo (the tool I am using to bootstrap my RN application) &lt;em&gt;does not support&lt;/em&gt; linking libraries, so we can’t easily add things that require editing of the application bundle.&lt;/p&gt;

&lt;p&gt;To make things worse, the package resolving the issue I mentioned above also meant that all the glue code that &lt;code&gt;rollup-plugin-wasm&lt;/code&gt; generated to switch based on environment for browser + nodejs doesn’t work either, because if there is a call to &lt;code&gt;require("fs")&lt;/code&gt;, expo breaks. Same thing for attempts to polyfill &lt;code&gt;crypto&lt;/code&gt; in the correct environment with &lt;code&gt;crypto-browserify.&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing RN issues one by one
&lt;/h3&gt;

&lt;p&gt;Looking at &lt;code&gt;wasm_exec.js&lt;/code&gt;, we can see that it doesn’t need the entire &lt;code&gt;crypto&lt;/code&gt; package, just one single function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (!globalThis.crypto) {
    throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s much easier to polyfill with something like &lt;a href="https://github.com/kumavis/polyfill-crypto.getrandomvalues"&gt;polyfill-crypto.getrandomvalues&lt;/a&gt; (though using &lt;code&gt;Math.random()&lt;/code&gt; instead of proper entropy source…)&lt;/p&gt;

&lt;p&gt;We can swap out our existing polyfill for this implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import getRandomValues from "polyfill-crypto.getrandomvalues";
if (!globalThis.crypto) {
  globalThis.crypto = {
    getRandomValues,
  };
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the import no longer breaks nodejs, browser, or react-native JavaScriptCore :)&lt;/p&gt;

&lt;p&gt;Loading the &lt;code&gt;.wasm&lt;/code&gt; file is a different beast, and the polyfills for &lt;code&gt;FileReader.readAsArrayBuffer&lt;/code&gt; caused expo to crash consistently. So, what do we do if we can’t load something from disk in the format we need? We play dirty: Instead of async loading the &lt;code&gt;.wasm&lt;/code&gt; file like a good citizen, we base64 that chunker and stuff it into our main JavaScript file!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rollup-plugin-wasm&lt;/code&gt; even has an option to inline wasm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wasm({
  sync: ["main.wasm"],
  targetEnv: "auto-inline",
  maxFileSize: 0,
}),

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result will make any editor without proper optimizations for uber-large files crawl&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6EYmSrUv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/wasm-base64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6EYmSrUv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/wasm-base64.png" alt="Delicious base64 blobs" width="880" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;… but it does work inside the Simulator! We’re no longer dependent on asset loading logic anymore. It’s all just js now.&lt;/p&gt;

&lt;p&gt;To fix remaining &lt;code&gt;FileLoader&lt;/code&gt; issues, there is &lt;a href="https://github.com/acostalima/react-native-polyfill-globals/blob/master/patches/react-native%2B0.63.3.patch"&gt;this patch&lt;/a&gt; available that polyfills the functionality. This patch alone doesn’t work to load the wasm file from disk and crashes the simulator, but it does work for smaller under-the-hood functionality that relies on &lt;code&gt;readAsArrayBuffer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With that out of the way, my app finally runs in the Simulator 🎉&lt;/p&gt;

&lt;p&gt;I thought this would be the end of the story and continued chipping away on my app. After days of work, it was finally ready to run it on my iPhone to do a final test before release. Compiling everything… running…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aHMTohBJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/Screen%2520Shot%25202022-10-02%2520at%252023.14.08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aHMTohBJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/Screen%2520Shot%25202022-10-02%2520at%252023.14.08.png" alt="Screen Shot 2022-10-02 at 23.14.08" width="880" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You gotta be kidding. &lt;code&gt;WebAssembly&lt;/code&gt; does not exist at all in JavaScriptCore when running on an actual device, but it &lt;em&gt;does&lt;/em&gt; exist inside the Simulator. Apparently, Apple shut it down in recent iOS versions, but from reading through posts, this used to be available in the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  … the end? Did we fail? What next?
&lt;/h2&gt;

&lt;p&gt;I’ve been considering what next steps to take to get this working, but without proper WebAssembly support in JSC on devices, performance will always be subpar. Here are some thoughts and directions I investigated:&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 1: Fallback to WKWebView
&lt;/h4&gt;

&lt;p&gt;This is the most promising but also a bit absurd. We create a WebView, inject our wasm binary into the webview and execute it there, then relay messages (with &lt;code&gt;window.postMessage&lt;/code&gt;) back and forth with our main app.&lt;/p&gt;

&lt;p&gt;In my tests, this actually worked pretty well, with the problem that making something generic that &lt;em&gt;just works&lt;/em&gt; is a bit hard. Actually, polyfilling &lt;code&gt;window.WebAssembly&lt;/code&gt; would mean we need to somehow bridge &lt;code&gt;go.importObject&lt;/code&gt; into our webview. On top of that, we need to proxy the changes the wasm binary did (like modifying globalThis) back to our native app using a transparent message relay, so libraries that rely on it can &lt;em&gt;just work&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Without a proper polyfill, &lt;code&gt;window.WebAssembly&lt;/code&gt; won’t be available in global scope, so our &lt;code&gt;maskedemail&lt;/code&gt; NPM package won’t be able to use it.&lt;/p&gt;

&lt;p&gt;All in all, a lot of headache. Probably better to split native and bridged code altogether and write the glue code from scratch. (&lt;a href="https://github.com/inokawa/react-native-react-bridge"&gt;react-native-react-bridge&lt;/a&gt; tries to get around this by using a separate entrypoint that’s getting processed with babel)&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 2: wasm2js
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/WebAssembly/binaryen"&gt;binaryen&lt;/a&gt; includes a tool called &lt;code&gt;wasm2js&lt;/code&gt; that compiles wasm files back to JavaScript. I have tried this as well, but could not get it running with my library, probably due to complexity with some stuff like reflections and HTTP requests. Very promising but very hard to debug due to the way this tool spits out JavaScript. Either it works, or it doesn’t; for me, it didn’t. (Though the errors I got were actual Go errors related to HTTP, so it almost worked)&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 3: gopherjs
&lt;/h4&gt;

&lt;p&gt;Not a solution to wasm, but to make Go run within react-native. Instead of building a wasm binary, &lt;a href="https://github.com/gopherjs/gopherjs"&gt;gopherjs&lt;/a&gt; can output functional JavaScript so we don’t have to deal with WebAssembly runtime at all.&lt;/p&gt;

&lt;p&gt;Honestly, I think this might be the best option for code reusability until WebAssembly support is fully available in JSC.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ended up doing: WASM -&amp;gt; GopherJS
&lt;/h2&gt;

&lt;p&gt;I’ve tried all options listed above, and the GopherJS variant was the nicest to work with. Even better: The Go API for GopherJS is almost identical to WASM! For example:&lt;/p&gt;

&lt;p&gt;This is the WASM version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func list(this js.Value, args []js.Value) interface{} {
    handler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        resolve := args[0]
        reject := args[1]
        go func() {
            // (...) get maskedemails here (...)
            resolve.Invoke("Hi there")
        }
    })

    promiseConstructor := js.Global().Get("Promise")
    return promiseConstructor.New(handler)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the gopherjs version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func list(this *js.Object, args []*js.Object) interface{} {
    handler := js.MakeFunc(func(this *js.Object, args []*js.Object) interface{} {
        resolve := args[0]
        reject := args[1]
        go func() {
            // (...) get maskedemails here (...)
            resolve.Invoke(out)
        }()

        return nil
    })

    promiseConstructor := js.Global.Get("Promise")
    return promiseConstructor.New(handler)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, it’s so similar that I was able to flat-out generate the &lt;code&gt;gopherjs&lt;/code&gt; main.go file with this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.PHONY: generate-gopherjs
generate-gopherjs:
    rm -rf cmd/gopherjs
    mkdir -p cmd/gopherjs
    cp main.go cmd/gopherjs/
    sed -i '' 's/syscall\/js/github\.com\/gopherjs\/gopherjs\/js/g' cmd/gopherjs/main.go
    sed -i '' 's/FuncOf/MakeFunc/g' cmd/gopherjs/main.go
    sed -i '' 's/js\.Value/\*js\.Object/g' cmd/gopherjs/main.go
    sed -i '' 's/js\.Global()/js\.Global/g' cmd/gopherjs/main.go

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a bit of JavaScript, I was able to add an automatic fallback to the gopherjs version to &lt;code&gt;maskedemail-js&lt;/code&gt;, so it’s now usable even when &lt;code&gt;window.WebAssembly&lt;/code&gt; isn’t available (although ships with 2x 8MB files).&lt;/p&gt;

&lt;h2&gt;
  
  
  No WASM in react-native as of now
&lt;/h2&gt;

&lt;p&gt;A bit sad I wasn’t able to get my goal of using real WASM working within React Native, but learned a good bit with this adventure.&lt;/p&gt;

&lt;p&gt;For now, I’ll use GopherJS when I want to re-use Go code, but I hope we’ll see actual WebAssembly support in JSC on devices soon-ish. With all the WASM hype, I doubt it’ll take long.&lt;/p&gt;

&lt;p&gt;There is also stuff like &lt;a href="https://wasmer.io"&gt;Wasmer&lt;/a&gt; that can embed WASM binaries into other languages like Swift, so instead of going the JSC route, it should be possible to embed it into native modules directly or non-RN apps - for when we really want to re-use the same binary at different places. But that’s also doable with Golang, without WASM.&lt;/p&gt;

&lt;p&gt;The app is available at &lt;a href="https://maskedemailmanager.david.coffee"&gt;https://maskedemailmanager.david.coffee&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webassembly</category>
      <category>reactnative</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Relaying emails with Hide My Email back and forth</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Sat, 24 Sep 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/dvcrn/relaying-emails-with-hide-my-email-back-and-forth-585h</link>
      <guid>https://dev.to/dvcrn/relaying-emails-with-hide-my-email-back-and-forth-585h</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gielpyb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-teaser.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gielpyb5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-teaser.png" alt="Envelopes and Apple" width="880" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mirrored from my blog - &lt;a href="https://david.coffee/relaying-with-hide-my-email/"&gt;https://david.coffee/relaying-with-hide-my-email/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’m a big fan of one-time-use emails (as you can probably tell by previous posts on this blog). However, one thing that iCloud’s &lt;em&gt;Hide My Email&lt;/em&gt; is still unbeatable at compared to my own domain is full anonymity. When I sign up for a junk site to get free WiFi, or order at a restaurant, I don’t even want to expose my domain to them, because that domain is, well, registered to my real identity.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hide My Email&lt;/em&gt; on the other hand is fully anonymous: Emails come from “@icloud.com”, no name in the headers, nothing. Just a fully anonymous blank email. Great! Services can’t even filter for it (compared to mozillas solution, or duckduckgo), because filtering would mean shutting access to everything coming from icloud.com.&lt;/p&gt;

&lt;p&gt;Here’s one thing I wanted to get working for a while: Instead of having &lt;code&gt;&amp;lt;site or person&amp;gt; -&amp;gt; one-time-use Email (iCloud) -&amp;gt; Me&lt;/code&gt;, I want it the other way around: A one-time-use email that allows &lt;em&gt;me&lt;/em&gt; to anonymously send to a specific recipient whenever I send something to that email. Turns out you can actually do that in Apple’s Hide My Email! Let’s take a look how&lt;/p&gt;

&lt;h2&gt;
  
  
  How Hide My Email actually works
&lt;/h2&gt;

&lt;p&gt;Hide My Email is acting as a relay in between the user account and the sender. You don’t &lt;em&gt;actually&lt;/em&gt; send emails from that randomly generated @icloud.com address. In fact, you can’t. You can’t authenticate with that email on the iCloud SMTP server because that email is not yours.&lt;/p&gt;

&lt;p&gt;Here’s what I mean: Let’s compose a new email and use Hide My Email&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yUN-T9LQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yUN-T9LQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1.png" alt="New email with Hide My Email" width="880" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I click “Send”, the email arrives successfully with the hidden iCloud email as sender, as expected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IudtC_Iu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1-receive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IudtC_Iu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1-receive.png" alt="How the receiver sees the email" width="880" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;… but when we inspect the actually sent email, it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uj3Zgrx0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1-sent.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uj3Zgrx0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-1-sent.png" alt="How the email actually got sent" width="880" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Do you see it? The email never got sent from “&lt;a href="//mailto:heave_balks_0g@icloud.com"&gt;heave_balks_0g@icloud.com&lt;/a&gt;”, instead what Mail did is, it inserted a &lt;em&gt;relay address&lt;/em&gt; as the actual receiver of the email. That’s the long &lt;em&gt;test_at_davemail_io_5rvm…@icloud.com&lt;/em&gt; you see there. Just that Mail is doing this so transparently that you’ll likely never even notice what is happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relaying back and forth
&lt;/h3&gt;

&lt;p&gt;So now that we know this relay address, what happens when we send an email directly to it? Let’s try!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9Wdw-eby--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-manual-test.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9Wdw-eby--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-manual-test.png" alt="Manually composing a new email to the relay" width="880" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sending, and hey look at this! Doing it manually gives us the same result! (duh)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QSITjzwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-2-receive.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QSITjzwr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-test-2-receive.png" alt="How the email got received" width="880" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that &lt;code&gt;test_at_davemail_io_5rvrmv74x77007_62a85f4e@icloud.com&lt;/code&gt; is the relay address that iCloud mapped to &lt;em&gt;&lt;a href="mailto:test@davemail.io"&gt;test@davemail.io&lt;/a&gt;&lt;/em&gt; - Everything I send to it will always arrive at &lt;a href="//mailto:test@davemail.io"&gt;test@davemail.io&lt;/a&gt;, kind of like a mini portal that shovels content over to a specific sender.&lt;/p&gt;

&lt;p&gt;The other end will always see &lt;code&gt;heave_balks_0g@icloud.com&lt;/code&gt;, the other part of the relay, as sender. The final relay looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;heave_balks_0g@icloud.com&lt;/code&gt;: Relay &lt;em&gt;to&lt;/em&gt; my email&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_at_davemail_io_5rvrmv74x77007_62a85f4e@icloud.com&lt;/code&gt;: Relay to the recipient&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hide My Email is unique per recipient, but not fully random
&lt;/h3&gt;

&lt;p&gt;Another question you might have while following this is: Wait a moment, aren’t Hide My Email addresses random? Well, kind of.&lt;/p&gt;

&lt;p&gt;Each time you generate a hide-my-email-email (nice word), it will be random, but only per recipient: When I hide my email from &lt;a href="//mailto:plans@tripit.com"&gt;plans@tripit.com&lt;/a&gt;, it will &lt;em&gt;always&lt;/em&gt; be the same @icloud.com email, no matter how often I generate it. You can’t use iCloud to spam people, they’ll see everything coming from the same person.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cool, but what can we actually do with this information??
&lt;/h2&gt;

&lt;p&gt;Obviously this post was leading up to something, otherwise I would have not put so much time into writing it.&lt;/p&gt;

&lt;p&gt;Now that we know that relay addresses don’t change, we can use this knowledge to automate sending completely anonymous emails to specific senders without exposing our main domain, name or any other information.&lt;/p&gt;

&lt;p&gt;Even better: Using the relay even shields us from any mistakes, like accidentally leaking personal information in headers. It happened dozen times that I used “forward email” functionality in email clients to forward an email to a service, just to realize that the email service sent that email with my global account email, instead of the one-time-use email. (How would the service even know what email I want to use for forwarding?)&lt;/p&gt;

&lt;p&gt;Let’s take a service like &lt;a href="https://www.tripit.com/web"&gt;TripIt&lt;/a&gt; which gives me an email that I can send flight itineraries to: &lt;a href="//mailto:plans@tripit.com"&gt;plans@tripit.com&lt;/a&gt;. I can link an email to it like “&lt;a href="//mailto:tripit.com@mydomain.com"&gt;tripit.com@mydomain.com&lt;/a&gt;”, then whenever I send an email &lt;em&gt;from&lt;/em&gt; this email to &lt;a href="//mailto:plans@tripit.com"&gt;plans@tripit.com&lt;/a&gt;, it will add that itinerary to my account. This works great, but means I can’t really setup any quick-actions, shortcuts or automations to quickly forward to it, because of the issue I described above. Now we can fix that by directly using the relay address :)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send an email to &lt;a href="//mailto:plans@tripit.com"&gt;plans@tripit.com&lt;/a&gt; using Hide My Email&lt;/li&gt;
&lt;li&gt;Copy the generated @icloud.com address and add it to TripIt&lt;/li&gt;
&lt;li&gt;Copy the generated relay address (the long one aka &lt;code&gt;test_at_davemail_io_5rvrmv74x77007_62a85f4e@icloud.com&lt;/code&gt;), create a new contact with it for quick access&lt;/li&gt;
&lt;li&gt;Profit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we can hit “forward” (or use a Siri Shortcut) to forward any PDF attachment we want to add to tripit to our relay address, iCloud will do the rest.&lt;/p&gt;

&lt;p&gt;All of this effort just so we don’t expose our main email, huh 😉&lt;/p&gt;

&lt;p&gt;Keep on shaving that yak&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N5ICEzmG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-yak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N5ICEzmG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/hide-my-email-yak.png" alt="The Yak" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Teaching my UniFi UDM how to IPv6</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Wed, 18 May 2022 06:26:28 +0000</pubDate>
      <link>https://dev.to/dvcrn/teaching-my-unifi-udm-how-to-ipv6-4821</link>
      <guid>https://dev.to/dvcrn/teaching-my-unifi-udm-how-to-ipv6-4821</guid>
      <description>&lt;p&gt;&lt;em&gt;Mirrored from my blog: &lt;a href="https://david.coffee/teaching-my-unifi-udm-how-to-ipv6/"&gt;https://david.coffee/teaching-my-unifi-udm-how-to-ipv6/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I recently ventured into IPv6 territory with one goal: Get rid of the router my ISP gave me.&lt;/p&gt;

&lt;p&gt;I am a fan of UniFi gear and own an &lt;em&gt;UniFi Dream Machine&lt;/em&gt; (upgraded from a UniFi Secure Gateway last year) which is more than capable of handling IPv6, so how hard can it be?&lt;/p&gt;

&lt;p&gt;I went into this not knowing a thing about IPv6 and just assumed it was identical to IPv4, just bigger, &lt;strong&gt;but boy was I wrong.&lt;/strong&gt; This is my writeup of what I learned and what my endresult is (spoiler: I got it working)&lt;/p&gt;

&lt;h2&gt;
  
  
  IPv6 misconceptions
&lt;/h2&gt;

&lt;p&gt;As I explained above, I just assumed that IPv6 was IPv4, but longer. So you have some gateway/subnet-mask combo, your router gets an IP assigned from the ISP and all devices inside the network use that IP outwards facing when interacting with things (= “NAT”). Internally, the router assigns IPv4 addresses based on the gateway/subnet-mask, with each device receiving a unique IPv4 address from the router through DHCP. So far so good.&lt;/p&gt;

&lt;p&gt;IPv6 on the other hand does not work at all like this. In IPv6-land, each &lt;strong&gt;device&lt;/strong&gt; receives unique (actually multiple) IPv6 addresses. There is no NAT, so when my iPhone that’s connected to my WiFi communicates with a website, that website will see the unique IPv6 address my iPhone has assigned, not my router’s IP. There are also (unless specifically setup) no local network addresses - each IPv6 address is global. And how can the router assign IPv6 addresses? Enter prefix delegation or IPv6-PD.&lt;/p&gt;

&lt;p&gt;The router, after authenticating with the ISP when PD is enabled, receives an entire address space (like /64 or /48) delegated to it. In simple words, the ISP tells the router: “Here’s a chunk of addresses that you can use for your devices, handle it.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Not all IPv6 addresses are created equal
&lt;/h3&gt;

&lt;p&gt;Let’s take a look at this ifconfig output of my Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;en0: flags=8863&amp;lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&amp;gt; mtu 1500
        options=50b&amp;lt;RXCSUM,TXCSUM,VLAN_HWTAGGING,AV,CHANNEL_IO&amp;gt;
        ether 4c:20:b8:e7:3b:be
        inet6 fe80::1449:6fa5:65a9:a313%en0 prefixlen 64 secured scopeid 0x6
        inet6 fd74:d39f:a55f::8c prefixlen 64 dynamic
        inet 192.168.1.2 netmask 0xffffff00 broadcast 192.168.1.255
        inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 dynamic
        inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 autoconf secured
        inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 autoconf temporary

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn’t just have 1 IPv6 address, no that would be far too simple. Instead, it has 5!! Why? Because apparently, we have so many IPv6 addresses now that we don’t need to be sparse with them /s.&lt;/p&gt;

&lt;p&gt;There are different kinds of IPv6 addresses assigned to my mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 dynamic
inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 autoconf secured
inet6 240f:78:&amp;lt;snip&amp;gt; prefixlen 64 autoconf temporary

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;secured&lt;/code&gt; address is my permanent IPv6 address. This address rarely ever changes and will stick around even after reboots.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;temporary&lt;/code&gt; address is, you guessed it, a temporary address.&lt;/p&gt;

&lt;p&gt;Lastly we have 2 more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet6 fe80::1449:6fa5:65a9:a313%en0 prefixlen 64 secured scopeid 0x6
inet6 fd74:d39f:a55f::8c prefixlen 64 dynamic

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fe80::&lt;/code&gt;, while not obvious whatsoever, is a prefix for &lt;code&gt;localhost&lt;/code&gt; or local link. This is an address that’s only usable locally.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fd74::&lt;/code&gt;, more specifically the &lt;code&gt;fd::&lt;/code&gt; prefix is designated for the local network. Didn’t I just earlier say that there are no local network addresses in IPv6? By default, yes, but I specifically setup my router to hand out local network addresses. Keyword here is “&lt;a href="https://en.wikipedia.org/wiki/Unique_local_address"&gt;Unique local address&lt;/a&gt;” (ULA).&lt;/p&gt;

&lt;h2&gt;
  
  
  IPv6 implementations - Native, 6to4, IPv6oE
&lt;/h2&gt;

&lt;p&gt;Are you still with me?? Okay, because there is more. Not all IPv6 implementations are equal either. Very common implementations are either IPv6 “native” or “IPv6-in-IPv4 tunnel”. There are more exotic implementations like SoftBanks “IPv6高速ハイブリッド IPv6 IPoE + IPv4” that’s utilizing IPoE for authentication and assigning addresses. (&lt;em&gt;Note here: I used SoftBank in the past and was not able to get IPv6 without their router working due to the proprietary way their router authenticates&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;I’m not going into detail here and don’t fully understand everything either, but a 6to4 tunnel is like the name implies an earlier implementation that tries to build the IPv6 stack on top of a IPv4 network stack. Read more about it &lt;a href="https://support.huawei.com/enterprise/en/doc/EDOC1100055124/58a0a1e4/understanding-ipv6-over-ipv4-tunnel-technology"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;“Native” IPv6 is the one that’s nice to work with, but also the one not every ISP supports. I made sure to pick an ISP that has a native IPv6 stack and doesn’t use IPv6-over-IPv4&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started - figuring out what my ISP router is doing
&lt;/h2&gt;

&lt;p&gt;The first step to getting IPv6 working was to actually figure out what my ISP router is doing when I plug it in. Specifically: How does it actually get the IPv6 prefix assigned, how does it authenticate, and what does the handhshake look like.&lt;/p&gt;

&lt;p&gt;Now how do we actually do this? &lt;a href="https://www.wireshark.org"&gt;Wireshark&lt;/a&gt; and a couple of USB-C network dongles to the rescue!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jkwI9BaD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/dongles.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jkwI9BaD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/dongles.jpg" alt="dongles" width="880" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mac has a lot of nice networking features, one of them is the ability to bridge 2 network interfaces, effectively creating a tunnel between them. This can be done through the CLI, but also through the network preferences pane after plugging multiple network dongles into the Mac.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e3wv5cZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/network-preference-pane.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e3wv5cZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/network-preference-pane.png" alt="network-preference-pane" width="880" height="839"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specifically, we want to make it so the MacBook is &lt;strong&gt;in between&lt;/strong&gt; the ISP Router and the network out and acting as a transparent bridge. So &lt;strong&gt;Internet &amp;lt;-&amp;gt; MacBook &amp;lt;-&amp;gt; ISP Router&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the virtual bridge is created, we can use Wireshark to monitor the new device for any traffic that happens between the ISP router and the mothership. Now start Wireshark, plug the ISP router in, and….&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aCh6B_Ia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/au-hikari-startup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aCh6B_Ia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/au-hikari-startup.png" alt="au-hikari-startup" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;… it works!&lt;/p&gt;

&lt;p&gt;Lot’s of new information here for a non-network engineer like me, but also some familiar words: DHCPv6, ICMPv6, UDP, SSL. The &lt;code&gt;*v6&lt;/code&gt; protocols are IPv6 variants of the same things we have in IPv4, but beefed up and more important in IPv6 land.&lt;/p&gt;

&lt;p&gt;Specifically, &lt;code&gt;ICMPv6&lt;/code&gt; is much more important than the IPv4 version that we use for pinging stuff and is used extensively for the &lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol"&gt;Neighbor Discovery Protocol&lt;/a&gt;&lt;/em&gt;, that’s those “Router Solicitation” and “Router Advertisement” messages you see in the screenshot above.&lt;/p&gt;

&lt;p&gt;So from this startup dump we can see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My ISP router inquires with ISP uplink through &lt;em&gt;Router Solicitation&lt;/em&gt; where the uplink router is&lt;/li&gt;
&lt;li&gt;Uplink responds with &lt;em&gt;Router Advertisement&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;ISP router advertises itself through DHCPv6, uses a DUID (“DHCP unique identifier”) to identify itself, this DUID is generated off the mac address&lt;/li&gt;
&lt;li&gt;ISP responds to DHCPv6 request and assigns IPv6 prefix&lt;/li&gt;
&lt;li&gt;Neighbor Discovery Protocol continues with Neighbor Solicitation and Neighbor Advertisement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(the XID stuff you see in there is a transaction id that’s being calculated on each request)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So to summarize&lt;/strong&gt; : This ISP is using fully native IPv6, the IPv6 prefix space is assigned through DHCPv6, and authentication happens through a DUID identifier that’s being used in all DHCPv6 requests.&lt;/p&gt;

&lt;p&gt;If we are able to replicate this handshake on the UDM, we should have IPv6 working. Now how do we actually do that…&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching the UDM how to DHCPv6
&lt;/h2&gt;

&lt;p&gt;From the wireshark dump above, we know that the ISP router is using DHCPv6 to authenticate and get its prefix assigned. We also know which DUID the ISP router uses for identifying itself, and we know the prefix size that we get assigned from the ISP (/48).&lt;/p&gt;

&lt;p&gt;Let’s enable IPv6 through DHCPv6 through the UniFi admin on WAN:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ila4Rc_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ila4Rc_---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-settings.png" alt="ipv6-settings" width="880" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then grep for &lt;code&gt;dhcp&lt;/code&gt; to see what’s happening on the UDM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ps aux | grep dhcp
 3537 root /sbin/udhcpc --foreground --interface eth4 --script /usr/share/ubios-udapi-server/ubios-udhcpc-script -r 124.214.62.19
 3895 root /usr/sbin/odhcp6c -R -f -e -v -s /usr/share/ubios-udapi-server/ubios-odhcp6c-script -P 48 eth4
 7715 root grep dhcp

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UDM is spinning up &lt;code&gt;udhcpc&lt;/code&gt; and &lt;code&gt;odhcp6c&lt;/code&gt; with the closed-source binaries &lt;code&gt;ubios-odhcp6c-script&lt;/code&gt;, both on the &lt;code&gt;eth4&lt;/code&gt; interface (my WAN port).&lt;/p&gt;

&lt;p&gt;We can also print our own &lt;code&gt;duid&lt;/code&gt; by running &lt;code&gt;cat /config/dnsmasq.lease&lt;/code&gt;, and since the DUID being used is not the one that our ISP router uses, we have to do something about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/openwrt/odhcp6c"&gt;odhcp6c&lt;/a&gt; that is being used here is is a DHCPv6 client by openwrt and it being open-source and widely used means we can dig deeper into what it’s doing with all those parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usage: odhcp6c [options] &amp;lt;interface&amp;gt;

Feature options:
        -S &amp;lt;time&amp;gt; Wait at least &amp;lt;time&amp;gt; sec for a DHCP-server (0)
        -N &amp;lt;mode&amp;gt; Mode for requesting addresses [try|force|none]
        -P &amp;lt;length&amp;gt; Request IPv6-Prefix (0 = auto)
        -F Force IPv6-Prefix
        -V &amp;lt;class&amp;gt; Set vendor-class option (base-16 encoded)
        -u &amp;lt;user-class&amp;gt; Set user-class option string
        -c &amp;lt;clientid&amp;gt; Override client-ID (base-16 encoded 16-bit type + value)
        -i &amp;lt;iface-id&amp;gt; Use a custom interface identifier for RA handling
        -r &amp;lt;options&amp;gt; Options to be requested (comma-separated)
        -R Do not request any options except those specified with -r
        -s &amp;lt;script&amp;gt; Status update script (/usr/sbin/odhcp6c-update)
        -a Don't send Accept Reconfigure option
        -f Don't send Client FQDN option
        -k Don't send a RELEASE when stopping
        -t &amp;lt;seconds&amp;gt; Maximum timeout for DHCPv6-SOLICIT (120)
        -m &amp;lt;seconds&amp;gt; Minimum time between accepting updates (30)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flag we’re after is &lt;code&gt;-c &amp;lt;clientid&amp;gt; Override client-ID (base-16 encoded 16-bit type + value)&lt;/code&gt;. So theoretically, if we are able to pass &lt;code&gt;-c&lt;/code&gt; into the startup of the &lt;code&gt;odhcp6c&lt;/code&gt; command, we should be able to receive a valid IPv6 address from the ISP. Let’s give it a try!&lt;/p&gt;

&lt;p&gt;Disable DHCPv6 again through the admin UI, wait until the process fully stopped, then manually run the command above, but with &lt;code&gt;-c&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/usr/sbin/odhcp6c -R -f -c &amp;lt;DUID from our wireshark dump&amp;gt; -e -v -s /usr/share/ubios-udapi-server/ubios-odhcp6c-script -P 48 eth4

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait a bit, then &lt;code&gt;ip -6&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ip -6 addr show scope global
10: br0: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu 1500 state UP qlen 1000
    inet6 fd74:d39f:a55f::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 240f:78:&amp;lt;snip&amp;gt;/64 scope global dynamic
       valid_lft 108367sec preferred_lft 108367sec

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hello there, IPv6 :)&lt;/p&gt;

&lt;p&gt;Back on the UDM admin, let’s enable IPv6 on the LAN network. We want to do prefix delegation and also advertise ourselves as a router (remember &lt;em&gt;Neighbor Discovery Protocol&lt;/em&gt; above?)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bb-6zEWx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-lan-settings.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bb-6zEWx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-lan-settings.png" alt="ipv6-lan-settings" width="880" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait a bit, then let’s enable IPv6 on my mac and renew the DHCPv6 lease:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;en0: flags=8863&amp;lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&amp;gt; mtu 1500
        inet6 240f:78:9464::&amp;lt;snip&amp;gt; prefixlen 64 dynamic
        inet6 240f:78:9464:0:&amp;lt;snip&amp;gt; prefixlen 64 autoconf secured
        inet6 240f:78:9464:0:&amp;lt;snip&amp;gt; prefixlen 64 autoconf temporary

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice!!&lt;/p&gt;

&lt;p&gt;And what about &lt;a href="https://test-ipv6.com"&gt;https://test-ipv6.com&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R4Y2CVZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-test.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R4Y2CVZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/assets/2022-05-18%2520IPv6%2520adventures.assets/ipv6-test.png" alt="ipv6-test" width="880" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Persisting changes to the UDM
&lt;/h3&gt;

&lt;p&gt;Now that we got an IPv6 address, let’s persist those changes so we don’t have to manually run odhcpv6 every time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pre-req: Extending the UDM with boot-scripts
&lt;/h4&gt;

&lt;p&gt;UniFi gear is &lt;em&gt;hackable&lt;/em&gt; which allows us to do more than just what’s possible through the admin UI. The USG had a very advanced customization flow baked in, but the UDM is a tad more annoying to work with.&lt;/p&gt;

&lt;p&gt;Luckily there is this awesome project called &lt;a href="https://github.com/boostchicken-dev/udm-utilities"&gt;udm-utilities&lt;/a&gt; by boostchicken, a “&lt;em&gt;A collection of things to enhance the capabilities of your Unifi Dream Machine or Dream Machine Pro.&lt;/em&gt;”&lt;/p&gt;

&lt;p&gt;Basically what this project does is, it installs a custom package that extends the UDM with the ability to run scripts on boot that persist in between reboots. We put our shell scripts into &lt;code&gt;/mnt/data/on_boot.d&lt;/code&gt;, and udm-utilities makes sure those are run when the router boots.&lt;/p&gt;

&lt;p&gt;There are a lot of really cool things you can do with it, from configuring the UDM to even running podman containers like homebridge or adguard, but we’ll be focusing on the network aspects only for this writeup.&lt;/p&gt;

&lt;h4&gt;
  
  
  Changing DUID on boot
&lt;/h4&gt;

&lt;p&gt;Now the next problem is, how do we actually start &lt;code&gt;odhcp6c&lt;/code&gt; with our own options? The UDM launches it on boot through &lt;code&gt;ubios-udapi-server&lt;/code&gt;, which is closed-source. We could decompile the binary with ghidra and see if we can hack our own arguments in, how hard can it be?&lt;/p&gt;

&lt;p&gt;But there is an easier (more hacky) way of achieving just that. What if the binary at &lt;code&gt;/usr/sbin/odhcp6c&lt;/code&gt; isn’t actually a binary, but an executable script that runs &lt;code&gt;odhcp6c&lt;/code&gt; for us? The UDM will continue to execute &lt;code&gt;odhcp6c&lt;/code&gt; like usual and we can inject our custom flags. Here’s what I mean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cat /usr/sbin/odhcp6c
#!/bin/sh
# workaround script to inject odhcp6 options
/usr/sbin/odhcp6c-real -R -f -c 0003000&amp;lt;snip&amp;gt; "$@"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom script that we put in &lt;code&gt;/usr/sbin&lt;/code&gt; is calling &lt;code&gt;odhcp6c&lt;/code&gt; transparently and adds the extra &lt;code&gt;-c&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;udm-utilities&lt;/code&gt;, we can automate this with the following boot script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/sh
mv /usr/sbin/odhcp6c /usr/sbin/odhcp6c-real
cp /mnt/data/odhcp6c/odhcp6c /usr/sbin/odhcp6c
chmod +x /usr/sbin/odhcp6c
killall odhcp6c

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;We move &lt;code&gt;odhcp6c&lt;/code&gt; to &lt;code&gt;odhcp6c-real&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy the custom &lt;code&gt;odhcp6c&lt;/code&gt; to the old location&lt;/li&gt;
&lt;li&gt;Make it executable&lt;/li&gt;
&lt;li&gt;Kill odhcp6c&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once killed, &lt;code&gt;ubios-udapi-server&lt;/code&gt; will automatically attempt to restart it by executing &lt;code&gt;/usr/sbin/odhcp6c&lt;/code&gt;, which is no longer the real binary but our custom script.&lt;/p&gt;

&lt;p&gt;And does it work?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ps aux | grep dhcp
 3891 root {odhcp6c} /bin/sh /usr/sbin/odhcp6c -e -v -s /usr/share/ubios-udapi-server/ubios-odhcp6c-script -P 64 eth4
 3895 root /usr/sbin/odhcp6c-real -R -f -c 0003000&amp;lt;snip&amp;gt; -e -v -s /usr/share/ubios-udapi-server/ubios-odhcp6c-script -P 64 eth4

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It absolutely does!&lt;/p&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;Worth it in terms of performance or network benefit? Probably not. The world doesn’t feel different when interacting with IPv6 and speed for me is pretty much on par as it was with IPv4.&lt;/p&gt;

&lt;p&gt;But it has been a very nice learning experience. Without this venture into IPv6-land I would have never learned about the differences between IPv6 and IPv4.&lt;/p&gt;

&lt;p&gt;My network is now fully IPv6 compatible which it wasn’t before, and that’s an achievement in itself.&lt;/p&gt;

</description>
      <category>ipv6</category>
      <category>unifi</category>
      <category>networking</category>
    </item>
    <item>
      <title>Some tiny personal programs I've written</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Tue, 22 Mar 2022 09:14:12 +0000</pubDate>
      <link>https://dev.to/dvcrn/some-tiny-programs-ive-written-fa2</link>
      <guid>https://dev.to/dvcrn/some-tiny-programs-ive-written-fa2</guid>
      <description>&lt;p&gt;&lt;em&gt;Mirrored from my blog - &lt;a href="https://david.coffee/some-tiny-programs-i-ve-written"&gt;https://david.coffee/some-tiny-programs-i-ve-written&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There was this post on HN the other day titled &lt;a href="https://jvns.ca/blog/2022/03/08/tiny-programs/"&gt;Some tiny personal programs I've written&lt;/a&gt; that I enjoyed a lot. It resonated with me so much because I've been pumping out smaller programs left and right for a while as well, but never thought of sharing those with the public, so let's change that. &lt;/p&gt;

&lt;p&gt;I love hacking on mini projects. Fixing a problem with programming feels like a superpower - I can do something that other people that can't program can't do, and every time I find a situation where I can apply my skills to an issue, I feel happy. &lt;/p&gt;

&lt;p&gt;Here are some programs I've written in the past few months, in no specific order:&lt;/p&gt;

&lt;h2&gt;
  
  
  a covid bot
&lt;/h2&gt;

&lt;p&gt;Every day when there were new cases published, people posted Twitter links, links to news and other sources. Turns out Tokyo has an &lt;a href="https://stopcovid19.metro.tokyo.lg.jp/"&gt;official dashboard&lt;/a&gt; that pulls data out of a GitHub repository and is updated daily. &lt;/p&gt;

&lt;p&gt;About an hour after discovering those json files, we had a slackbot that posts updates once daily - nice!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PgLC9LQ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/coronachan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PgLC9LQ8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/coronachan.png" alt="The COVID bot in action" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  apartment finder LINE bot
&lt;/h2&gt;

&lt;p&gt;This one I'm especially proud of because I was able to get my current place thanks to it. &lt;/p&gt;

&lt;p&gt;In Tokyo there are 2 'companies' that are owned by the government, and rent out pretty decent places for an ok price, without hidden costs, minimun contract runtime, cancellation fee, and so on. Only problem - these place are gone &lt;em&gt;fast&lt;/em&gt;. Like in minutes after something is posted, it's gone. &lt;/p&gt;

&lt;p&gt;People don't even bother visiting these places, they just put in an application right away, then visit them and cancel accordingly. &lt;/p&gt;

&lt;p&gt;At first I was checking the page every couple hours once, but that was too inefficient, so I hacked together a bot that's using &lt;a href="https://github.com/puppeteer/puppeteer"&gt;puppeteer&lt;/a&gt; to check for new place, and send me a message on LINE whenever something new is available. A couple weeks later and my dream place free'd up!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XiRxB8yu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/apatochan.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XiRxB8yu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/apatochan.jpeg" alt="Apartment bot in action" width="880" height="1798"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fastmail masked aliases through the cli
&lt;/h2&gt;

&lt;p&gt;I wrote a &lt;a href="https://david.coffee/creating-fastmail-masked-emails-without-1password-anytime-and-everywhere"&gt;separate blogpost&lt;/a&gt; about this one so I'll skip the details, but when Fastmail + 1Password released their 'masked email' (domain specific email addresses) implementation I was stoked because I was doing something like this for a long long time. I wanted to migrate to this new feature, but it wasn't available to the public yet, and only usable through 1Password. &lt;/p&gt;

&lt;p&gt;A put a couple hours in to reverse-engineer the fastmail JMAP API with proxyman and hacked together a Golang client that was able to create identical requests. Now I have masked emails everywhere I want by just hammering '@@@' into a dialogue. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PF3dW7qw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://blotcdn.com/blog_f1c3613082cc4491b8eddec3b73ef2fe/_image_cache/3f698dde-7569-4a97-af6a-95825955d3e8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PF3dW7qw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://blotcdn.com/blog_f1c3613082cc4491b8eddec3b73ef2fe/_image_cache/3f698dde-7569-4a97-af6a-95825955d3e8.gif" alt="Fastmail masked emails through Keyboard Maestro" width="880" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  work-hour tracking through the cli
&lt;/h2&gt;

&lt;p&gt;At work we use this tool called &lt;a href="https://www.kingoftime.jp/"&gt;King of Time&lt;/a&gt; to track working hours. It's usable through the web app but doing bulk edits was very tedious. I wanted to be able to hook checkins/checkouts to different events and actions, and the lack of an official API made this difficult. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/puppeteer/puppeteer"&gt;puppeteer&lt;/a&gt; to the rescue (it involved way too much javascript to be usable without headless browser), and tadaa - a npm-installable company internal tool to interact with King of Time was born.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EZNtPDRl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/kot-cli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EZNtPDRl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://david.coffee/_Files/assets/Some%2520tiny%2520programs%2520I%27ve%2520written/kot-cli.png" alt="The time-tracking CLI" width="880" height="1051"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  auto-importing Japanese words into my study app
&lt;/h2&gt;

&lt;p&gt;When I was learning Japanese more actively I often encountered words I didn't know, as you usually do. I looked those up in a dictionary, and then usually added them to my flashcard app (Anki). &lt;/p&gt;

&lt;p&gt;This became repetitive fast, so I did what engineers do and hacked together a &lt;a href="https://github.com/dvcrn/ankimo"&gt;chrome extension&lt;/a&gt; that does it for me. Bonus points for writing it in ClojureScript which I wanted to get better with at that time. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KBBOoJ0v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/dvcrn/ankimo/raw/master/screenshots/tangorin.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KBBOoJ0v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/dvcrn/ankimo/raw/master/screenshots/tangorin.gif" alt="ankimo" width="880" height="921"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  auto-compound crypto
&lt;/h2&gt;

&lt;p&gt;This one is the definition of a micro-program. FTX is able to lend away unused crypto and gives interest back. I wanted this interest to be auto-compounded every hour without me having to open the site all the time and click some buttons. &lt;/p&gt;

&lt;p&gt;Luckily FTX has an API to do just that, so hooking it up to a script that runs in cron, and we have automatic compounding :) &lt;/p&gt;

&lt;h2&gt;
  
  
  HomeKit-ifying my TV and aircon
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://homebridge.io/"&gt;homebridge&lt;/a&gt; is an amazing piece of software. It allows to integrate basically anything that has an API into Apples HomeKit, which is what I use at home. &lt;/p&gt;

&lt;p&gt;Problem: Neither my Air-Conditioner, nor my TV are HomeKit-compatible. The TV is from a time way before HomeKit was even a thing (running 'netcast'), and my Air-Conditioner is only using through Sharps proprietory 'Cocoro Cloud'.&lt;/p&gt;

&lt;p&gt;2 homebridge plugins got created during longer caffeinated sessions, and now I can automate both purely in HomeKit: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/dvcrn/homebridge-netcast-tv"&gt;https://github.com/dvcrn/homebridge-netcast-tv&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/dvcrn/homebridge-sharp-cocoro"&gt;https://github.com/dvcrn/homebridge-sharp-cocoro&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  netatmo temperature getter
&lt;/h2&gt;

&lt;p&gt;I use a netatmo weather station at home and wanted to get it's temperature to integrate into HomeKit, so I can always see what the current temperature is, but also automate things like my AirCon or fans. This is another micro-script that just pulls the stations from my account and stores it in a text file (&lt;code&gt;/tmp/temperature&lt;/code&gt;), running in a CRON&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;station&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;dc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;station&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;homeModuleID&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Found home module"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;homeModule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%.1f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;homeModule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DashboardData&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Current temperature at home: %v &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tmp/temperature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ... and lots more!
&lt;/h2&gt;

&lt;p&gt;It's nice to just hack on things in between professional tasks and work. Low commitment, easy to explore new technology and usually makes something in my day-to-day life a bit easier. &lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>go</category>
      <category>automation</category>
    </item>
    <item>
      <title>VPN connections and local network traffic on Mac</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Fri, 21 Jan 2022 05:55:30 +0000</pubDate>
      <link>https://dev.to/dvcrn/vpn-connections-and-local-network-traffic-on-mac-1ien</link>
      <guid>https://dev.to/dvcrn/vpn-connections-and-local-network-traffic-on-mac-1ien</guid>
      <description>&lt;p&gt;Mirrored from my blog - &lt;a href="https://david.coffee/vpn-connections-and-local-network-traffic-on-mac"&gt;https://david.coffee/vpn-connections-and-local-network-traffic-on-mac&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These days I've been trying to be more conscious about my online footprint and privacy in general. I've been using one-off email addresses for years now (btw check out my recent post on &lt;a href="https://david.coffee/creating-fastmail-masked-emails-without-1password-anytime-and-everywhere"&gt;masked emails&lt;/a&gt; if you haven't yet), and have a bunch of little scripts that spit out things like random usernames when I hit "!" 3 times.&lt;/p&gt;

&lt;p&gt;But honestly speaking, IP addresses were one of those things I just kind of didn't really care about.&lt;/p&gt;

&lt;p&gt;What changed my view drastically was when I hacked on a &lt;a href="https://workers.cloudflare.com/"&gt;cloudflare workers&lt;/a&gt; project and for debug purpose decided to &lt;code&gt;console.log()&lt;/code&gt; the Request object. Here's what got printed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"cf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REDACTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REDACTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"continent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"tlsVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TLSv1.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"colo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NRT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REDACTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"edgeRequestKeepAliveStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"requestPriority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"httpProtocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP/3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"regionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"13"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"asOrganization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REDACTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"postalCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"REDACTED"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I cut out some of the details here, but the request had longitude, latitude, my internet provider name and a postal code that is &lt;em&gt;very&lt;/em&gt; close to mine. Meaning &lt;em&gt;every site&lt;/em&gt; I interact with basically knows where I live to a certain degree. That ain't good. &lt;/p&gt;

&lt;p&gt;I now have a VPN running almost all of the time, which brings me to the main topic of this post:&lt;/p&gt;

&lt;p&gt;When using a VPN, like NordVPN, and you want to let that run 24/7 so that you can move all traffic without leaks through the it, you'll sooner or later end up running into issues with things like local network no longer resolving because it's not part of the virtual network.&lt;/p&gt;

&lt;p&gt;How do you fix that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Temporary rules with route
&lt;/h2&gt;

&lt;p&gt;Mac has a bunch of neat network tools that you can use to get around this, the easiest is &lt;code&gt;route&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# route &amp;lt;target&amp;gt; &amp;lt;gateway&amp;gt;
sudo route -nv add -net 10.0 192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the &lt;code&gt;-net&lt;/code&gt; flag (there is also &lt;code&gt;-host&lt;/code&gt;), &lt;code&gt;10.0&lt;/code&gt; expands to &lt;code&gt;10.0.0.0&lt;/code&gt;, so the entire subnet of &lt;code&gt;10.0.*&lt;/code&gt; will get routed to the gateway &lt;code&gt;192.168.1.1&lt;/code&gt; instead of the VPN network, sweet! (10.0.5.* is my network for services and smaller devices)&lt;/p&gt;

&lt;p&gt;Routes added with &lt;code&gt;route&lt;/code&gt; are temporary, so once you restart they're gone which makes them good for experimenting and quickly setting things up in a non-destructive way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making things persist
&lt;/h2&gt;

&lt;p&gt;For persistent rules, mac comes with the &lt;code&gt;networksetup&lt;/code&gt; tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ networksetup -listallnetworkservices

An asterisk (*) denotes that a network service is disabled.
Ethernet
USB 10/100/1000 LAN
Wi-Fi
Thunderbolt Bridge
NordVPN NordLynx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command we want is &lt;code&gt;-setadditionalroutes&lt;/code&gt;. This command takes a interface, and then triplets of &lt;code&gt;&amp;lt;target&amp;gt; &amp;lt;netmask&amp;gt; &amp;lt;gateway&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;networksetup -setadditionalroutes "Ethernet" 10.0.5.0 255.255.255.0 192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command adds a route for &lt;code&gt;10.0.5.*&lt;/code&gt; (designated by the netmask &lt;code&gt;255.255.255.0&lt;/code&gt;) and routes all traffic to the gateway &lt;code&gt;192.168.1.1&lt;/code&gt; instead. &lt;/p&gt;

&lt;p&gt;Repeat that for all your interfaces that you use to connect, and done.&lt;/p&gt;

&lt;p&gt;To add multiple routes you'll have to specify them in the same go, in triplets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;networksetup -setadditionalroutes "Ethernet" 192.168.0.0 255.255.0.0 192.168.1.1 10.0.5.0 255.255.255.0 192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now everything from &lt;code&gt;192.168.*&lt;/code&gt; and &lt;code&gt;10.0.5.*&lt;/code&gt; will get routed to my router&lt;/p&gt;

&lt;h4&gt;
  
  
  About NordVPN
&lt;/h4&gt;

&lt;p&gt;I never talked to NordVPN, but after trialing 4-5 different VPN services that were recommended for privacy, I ended up at Nord and am very happy with them (dark patterns like difficulty to unsubscribe aside). The speed is very consistent and it's one of the only options I was really able to keep turned on 24h a day.&lt;/p&gt;

&lt;p&gt;If you're thinking of signing up for NordVPN, consider using &lt;a href="https://ref.nordvpn.com/pSuUglACVGO"&gt;my referal&lt;/a&gt;&lt;/p&gt;

</description>
      <category>network</category>
      <category>vpn</category>
      <category>firewall</category>
    </item>
    <item>
      <title>Creating Fastmail Masked Emails without 1Password anytime and everywhere</title>
      <dc:creator>David Mohl</dc:creator>
      <pubDate>Fri, 17 Dec 2021 06:35:15 +0000</pubDate>
      <link>https://dev.to/dvcrn/creating-fastmail-masked-emails-anywhere-and-everywhere-1b30</link>
      <guid>https://dev.to/dvcrn/creating-fastmail-masked-emails-anywhere-and-everywhere-1b30</guid>
      <description>&lt;p&gt;&lt;em&gt;Mirrored from my blog - &lt;a href="https://david.coffee/creating-fastmail-masked-emails-without-1password-anytime-and-everywhere" rel="noopener noreferrer"&gt;https://david.coffee/creating-fastmail-masked-emails-without-1password-anytime-and-everywhere&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I love Masked Emails, Fastmails take on one-time-use email addresses. I've been doing something similar myself for years now, but it is a bit of a shame that it's so tightly coupled to 1Password. Sure you &lt;em&gt;can&lt;/em&gt; create them through the webinterface now, but it's still kind of a pain to use and not very convenient (unless you use 1Password). &lt;/p&gt;

&lt;p&gt;You can't even manually trigger creation of one within 1Password, so when the extension doesn't detect an email signup field, you're sorta out of luck. &lt;/p&gt;

&lt;p&gt;What I want is something like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwii2jj73rhd5p772dpoa.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwii2jj73rhd5p772dpoa.gif" alt="showcase"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's get it working. &lt;/p&gt;

&lt;p&gt;The first thing I noticed is that the request to create a new masked email (through 1Password) isn't actually coming from the 1Password servers like I first thought, it goes directly from the client to api.fastmail.com, like seen on a reverse-proxy like Proxyman:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92b732w7yzgak1q0tudb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92b732w7yzgak1q0tudb.jpg" alt="Network screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, Fastmail also stated they put this functionality right into their open-source JMAP project &lt;a href="https://blog.1password.com/making-masked-email-with-jmap/" rel="noopener noreferrer"&gt;in a blogpost&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;After a bit of reverse-engineering the request format to the API server and replicating it in Golang:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel57zmcpp3qfw3xysnxt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel57zmcpp3qfw3xysnxt.jpg" alt="Requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I present you: &lt;a href="https://github.com/dvcrn/maskedemail-cli" rel="noopener noreferrer"&gt;https://github.com/dvcrn/maskedemail-cli&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maskedemail-cli -accountid xxxx -token abcdef12345 create facebook.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and you get a new masked email for facebook.com, right in your shell!&lt;/p&gt;

&lt;h2&gt;
  
  
  Glueing the CLI to shortcuts with KeyboardMaestro
&lt;/h2&gt;

&lt;p&gt;I wanted this to be available everywhere whenever I need to. A full extension for a product that isn't mine would have been too much work, and I wanted it to work outside browsers as well. With something like KeyboardMaestro this is trivial to do&lt;/p&gt;

&lt;p&gt;Here's the macro from above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf7tums456almy9l9pa8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf7tums456almy9l9pa8.png" alt="KeyboardMaestro Macro"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Insert "..."&lt;/li&gt;
&lt;li&gt;Retrieve the URL from the current browser window. I had to use some glue applescript here because Firefox has no scripting interface&lt;/li&gt;
&lt;li&gt;Pipe it into the maskedemail-cli&lt;/li&gt;
&lt;li&gt;Remove the "..." and paste the newly created email&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Other thoughts
&lt;/h2&gt;

&lt;p&gt;I am sure this will become more available in the near future. Fastmail likely has some kind of exclusivity thing with 1Password for now, but it's great to see that we can hack ontop of it so easily. It's also very cool that this is built right into JMAP. I hope we'll see more email providers implementing it!&lt;/p&gt;

&lt;p&gt;PS - I just wanted an excuse to write something on dev.to &lt;/p&gt;

</description>
      <category>reverseengineering</category>
      <category>go</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
