<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[Exerra's Blog]]></title><description><![CDATA[This is the blog of Exerra, A full-stack developer from Latvia.]]></description><link>https://blog.exerra.xyz/</link><item><title><![CDATA[Utilities -> Tools]]></title><link>https://blog.exerra.xyz/blog/utilities-now-tools</link><guid>https://blog.exerra.xyz/blog/utilities-now-tools</guid><description><![CDATA[<p><strong>Tools is accessible at - <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">tools.exerra.xyz</a></strong></p>
<p>Utilities had a lot of problems - some big, some small, so I eventually ended up making a replacement - <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>.</p>
<p>This post is meant to explain my thought process behind each decision and to ultimately, hopefully, be the first of many reports of old outdated projects being updated to my modern standards.</p>
<p>⚠️ <strong>Bit of a spoiler, but Utilities is no longer accessible as it redirects to <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>.</strong> ⚠️</p>
<h1 id="backstory">Backstory</h1>
<p>Back in late 2022 I had a problem. I was doing design work with software that didn't respect aspect ratios when resizing. It was relatively fine when I was just working with standard aspect ratios and standard resolutions (e.g. 1920x1080, 1280x720 when 16:9), but quickly became quite a large pain in the butt when working with non-standard aspect ratios or even non-standard resolutions <em>within <strong>standard</strong> aspect ratios</em>.</p>
<p>For a while I ended up either just guessing (which, of course, didn't go down that well), or calculating the missing height/width myself. It was fine for a while, but that became too tiresome as I would have to punch the formula and numbers in every time I needed to calculate it, and sometimes I even had forgotten the formula!</p>
<p>Now, thankfully, sometime during late 2022 I came to the realisation I am a programmer and that I can just simplify the whole thing... so I did. I made it mostly with just one category and one tool in mind, which, in hindsight, was the worst thing I could have done.</p>
<hr>
<h1 id="issues">Issues</h1>
<p>Websites have 3 categories with which we can label issues - design, performance and functionality. I will cover all 3 seperately, and then in a different section following the same format I will explain how I fixed it.</p>
<h2 id="design">Design</h2>
<p><img src="https://share.exerra.xyz/0HC5DpZ.png" alt="Utilities index page on desktop"></p>
<p>The design for Utilities was very simple, childish, colourful and full with paddings. At the time I had finished making a project which had a very similar design, and, in my opinion, a design that still fits the project. However, I made the fatal mistake of taking the same design, removing key elements that made it look good (e.g. images) and forcefully cramming it into Utilities.</p>
<p>The result was a very big mess that wasn't optimised for mobile at <em>all</em>.</p>
<p><img src="https://share.exerra.xyz/eRn5YoI.png" alt="Utilities index page and aspect ratio calculator on mobile"></p>
<p>As you can see in the image, the titles and descriptions are way too big, leading to a lot of scrolling and a general "zoomed-in" feeling. That especially sucks when you have to quickly make changes (e.g. you have multiple coefficients to calculate, and to calculate every one you have to scroll quite a lot).</p>
<p>In the index page the cards didnt make good use of the space. They had a title, a big icon and... that's it. Taking up like ¼ or ⅕ of the screen just for a title and icon is actual UI horror.</p>
<p>Now, I'm not sure how many people were on mobile (if there were any even), but I personally ended up using Utilities mostly on mobile, so these issues were too painful to ignore.</p>
<h2 id="performance">Performance</h2>
<p>With the increased latency of mobile data, navigating Utilities felt a bit sluggish on mobile as it was a NextJS multi-page application (MPA) rather than a single-page application (SPA). MPAs do have some benefits, especially if you have quite "light" pages (e.g. not a lot of stuff to load on each page), however due to the fact that Utilities was built with NextJS and thus with React, it did have some beefy JS to load.</p>
<h2 id="functionality">Functionality</h2>
<p>This is the most nitpicky of them all, but it does degrade the UX for mobile users - <strong>useless buttons</strong>.</p>
<p>Now, buttons themselves aren't useless if you have something that should only fire once and only fire when the user has inputted all of the data... however in this case it was useless.</p>
<p><img src="https://share.exerra.xyz/kHZeFJy.png" alt="Utilities data coefficient calculator on desktop"></p>
<p>Let's take for example the Death/Birth Coefficient Calculator. What difference does it make if the coefficient gets calculated every time you change a value, or if it gets calculated on a button press?</p>
<p>Simple math isn't resource intensive, so it is not that. It isn't destructive, so it also isn't that.</p>
<p><strong>There's really no reason to have the button.</strong> All it does, is generate extra steps for the user. Desktop users have to pick up their mouse, drag it to the button and click it. Mobile users have to scroll down the (very badly designed and zoomed-in) UI.</p>
<hr>
<h1 id="the-redesign">The redesign</h1>
<p>As the redesign ended up being quite professional, I ended up going with the name "<a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>". It is easier to type, has less characters to read and just rolls off the tongue easier.</p>
<h2 id="design-1">Design</h2>
<p><img src="https://share.exerra.xyz/0wMBZLo.png" alt="Tools index page on desktop"></p>
<p>For the design my main goal was to make it look clean, professional and tidy it up a little, a.k.a. get rid of the useless padding. For the design I ended up taking cues from <a href="https://ui.shadcn.com/" target="_blank" rel="nofollow">shadcn/ui</a>, which I have already used for <a href="https://chromeos.exerra.xyz" target="_blank" rel="nofollow">ChromeOS Releases</a> and although I ended up not using the library itself (due to performance considerations), I did end up copying the style a bit.</p>
<p>Now not only does the desktop experience get better as everything fits without having to scroll, but most importantly the mobile experience gets MUCH better.</p>
<p><img src="https://share.exerra.xyz/PzVIDRi.png" alt="Tools index page and aspect ratio calculator on mobile"></p>
<p>The index page is already packed with more information (descriptions), while still showing more cards than Utilities.</p>
<p>The tool page now finally relegates just a relatively small amount of the page for the title and description, leaving the rest to the input fields. Words cannot describe how much better this is, so I will just show you!</p>
<p><img src="https://share.exerra.xyz/jC1An0w.png" alt="Utilities aspect ratio calculator and Tools aspect ratio calculator on mobile"></p>
<p>As someone who really hates unnecessary actions and most importantly the "zoomed-in" feeling, the new design is just... awesome. I can't think of a better way to say it.</p>
<h2 id="performance-1">Performance</h2>
<p>Remember how I talked about MPAs and SPAs? Yeah, well that's not the only thing different now.</p>
<p><a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a> is now built with Astro and React. Astro is a very optimised and amazing framework that even, somehow, managed to optimise React. The difference between MPA performance of Astro (w/ React) and NextJS is already <em>insane</em>, however it gets better.</p>
<p><a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a> as an MPA works much better already, but what if I cached the important stuff and made it into an SPA? Well, I did!</p>
<p>Using <code>astro-spa</code> I managed to (very easily!!) convert <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a> from an MPA to an SPA. It doesn't really change much if you just directly go to a tool (especially because it doesn't really add that much JS), however if you go to the index and navigate to a tool, or just traverse the tools, you will immediately feel the... fastness?</p>
<p>⚠️ <em><strong>While writing this post I did end up finding a bug with the SPA.</strong> If you use <strong>Firefox</strong> the required scripts for the calculators don't load if you start on the <em>index</em> page, but if you start on a tool (which loads in the React scripts), everything works fine. I might end up disabling it on Firefox browsers if it keeps happening, but hopefully I can fix it</em></p>
<p>Edit: The bug affects more browsers than I thought. See: <a href="https://github.com/Exerra/tools/issues/3" target="_blank" rel="nofollow">issue #3 on GitHub</a></p>
<h2 id="functionality-1">Functionality</h2>
<p>As I alluded to before, <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a> now does the calculations automatically every time you input a new value into the fields. This makes the mobile experience much better, as theres no more scrolling, etc.</p>
<hr>
<h1 id="what-now">What now?</h1>
<p>Now is the migratory phase to <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>. I have already set up a redirect from Utilities to <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>. I could have just added a banner with a link to <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>, however I thought that I should rather just forcefully migrate users to <a href="https://tools.exerra.xyz" target="_blank" rel="nofollow">Tools</a>. I am sorry for anyone affected, but I imagine the slight inconvenience will be outweighed by the massive benefits of the new redesign.</p>
<p>I also have some ideas of what I can put into the calculator:</p>
<ul>
<li>Gamepad tester</li>
<li>Gradient + wave generator</li>
<li>Microphone tester (spectrum analyser + spectogram)</li>
<li>JSON to TS types</li>
</ul>
<p>These are just ideas, and if you have any ideas or just wanna see the progress, or maybe even contribute, the GitHub repository is <a href="https://github.com/Exerra/tools" target="_blank" rel="nofollow">Exerra/tools</a> :)</p>]]></description><pubDate>Tue, 12 Dec 2023 00:00:00 GMT</pubDate><media_content width="560" url="https://tools.exerra.xyz/og.png" /></item><item><title><![CDATA[How to send emails from Cloudflare Workers with MailChannels & DKIM]]></title><link>https://blog.exerra.xyz/blog/send-emails-cf-workers-email-routing</link><guid>https://blog.exerra.xyz/blog/send-emails-cf-workers-email-routing</guid><description><![CDATA[<p><strong>Mailchannels will be shutting down the Workers Email API on <code>July 1st 2024 @ 00:00 (UTC+0)</code>. This WILL cause all existing projects to break and any future projects won't work.</strong></p>
<p><strong>More information <a href="https://support.mailchannels.com/hc/en-us/articles/26814255454093-End-of-Life-Notice-Cloudflare-Workers" target="_blank" rel="nofollow">here</a>.</strong></p>
<hr>
<p>If you're using Cloudflare Email Routing alongside Cloudflare Workers, you've probably noticed an issue. You can only send or forward emails to approved email addresses. Fortunately, if you want to send emails to any address you want, <a href="https://blog.mailchannels.com/mailchannels-enables-free-email-sending-for-cloudflare-workers-customers" target="_blank" rel="nofollow">Cloudflare has partnered up with MailChannels</a> to provide every Cloudflare customer free email sending through a Cloudflare Worker.</p>
<p>This is a complete guide for sending emails using MailChannels through Cloudflare workers.</p>
<h1 id="creating-a-worker">Creating a worker</h1>
<p>First, make sure wrangler is installed. You can use the <a href="https://developers.cloudflare.com/workers/wrangler/get-started/" target="_blank" rel="nofollow">official guide</a></p>
<p>Then, create a Cloudflare worker project. For the sake of this guide, we will be using the TypeScript template.</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #D8DEE9FF">npm init cloudflare my-project worker-typescript</span></span>
<span class="line"><span style="color: #616E88"># or</span></span>
<span class="line"><span style="color: #D8DEE9FF">yarn create cloudflare my-project worker-typescript</span></span>
<span class="line"><span style="color: #616E88"># or</span></span>
<span class="line"><span style="color: #D8DEE9FF">pnpm create cloudflare my-project worker-typescript</span></span></code></pre>
<p>Then navigate into the directory by doing</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #88C0D0">cd</span><span style="color: #D8DEE9FF"> my-project</span></span></code></pre>
<hr>
<h1 id="setting-up-dns-records">Setting up DNS records</h1>
<p>Before we go any further, we need to generate DNS records for SPF and DKIM. I'll assume that Cloudflare has already auto generated the DMARC DNS record, and that you haven't touched it (since you really shouldn't!).</p>
<h2 id="spf">SPF</h2>
<p>A Sender Policy Framework (SPF) record is a type of DNS TXT record that lists all of the servers authorised to send emails from a particular domain.</p>
<p>Without this record, if we try send an email through MailChannels, email servers wouldn't know if MailChannels is authorised to send emails.</p>
<p>(Fun fact! I spent a week troubleshooting DKIM not working, because I forgot to add this!)</p>
<p><strong>If you DO NOT have an SPF DNS record already</strong> (which is unlikely), just create a new DNS TXT record, with the name being the domain you want to send emails from (if no subdomains, then just put <code>@</code>) and the value being:</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #d8dee9ff">v=spf1 a mx include:relay.mailchannels.net ~all</span></span></code></pre>
<p><strong>If you do</strong> (which you should already have if you set up Email Routing), add <code>include:relay.mailchannels.net</code> after the other includes. Example:</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #d8dee9ff">v=spf1 include:_spf.mx.cloudflare.net include:relay.mailchannels.net ~all</span></span></code></pre>
<p><img src="https://share.exerra.xyz/hDOWMYA.png" alt="Adding the TXT record"></p>
<p>Pro tip: You cannot have multiple SPF TXT records, so if you have to add another include, just append it in the existing TXT record.</p>
<h2 id="dkim">DKIM</h2>
<p>DKIM ensures that emails you sent are verifiably yours. Without DKIM, people could send emails in your name, and no one would know it's not you!</p>
<p><em>This part was taken from the wonderful GitHub repository <a href="https://github.com/maggie-j-liu/mail/tree/main#readme" target="_blank" rel="nofollow"><code>maggie-j-liu/mail</code></a></em></p>
<h3 id="generate-the-private-key">Generate the private key</h3>
<p>To generate a private key and upload it to the worker, run the following command</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #D8DEE9FF">openssl genrsa 2048 </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> tee priv_key.pem </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> openssl rsa -outform der </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> openssl base64 -A </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> wrangler secret put DKIM_PRIVATE_KEY</span></span></code></pre>
<p><code>openssl genrsa 2048</code> generates a 2048-bit RSA key.</p>
<p>The output of that is then passed on to <code>tee priv_key.pem</code>, which writes the key to the <code>priv_key.pem</code> file.</p>
<p>Then THAT gets passed on to <code>openssl rsa -outform der | openssl base 64 -A</code>, which converts the key from the PEM format to the DER format, then base64 encodes it (pretty much just removes the header from the PEM formatted key).</p>
<p>And, finally, the output of that gets passed on to <code>wrangler secret put DKIM_PRIVATE_KEY</code>, which adds the base64 encoded key as a secret in the Cloudflare Worker.</p>
<h3 id="creating-the-dns-record">Creating the DNS record</h3>
<p>Now we need to add the public key to a TXT record. Run the following command:</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #88C0D0">echo</span><span style="color: #D8DEE9FF"> -n </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">v=DKIM1;p=</span><span style="color: #ECEFF4">"</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">></span><span style="color: #D8DEE9FF"> record.txt </span><span style="color: #81A1C1">&#x26;&#x26;</span><span style="color: #D8DEE9FF"> openssl rsa -in priv_key.pem -pubout -outform der </span><span style="color: #81A1C1">|</span><span style="color: #D8DEE9FF"> openssl base64 -A </span><span style="color: #81A1C1">>></span><span style="color: #D8DEE9FF"> record.txt</span></span></code></pre>
<p>This creates a public key from the private key (<code>openssl rsa -in priv_key.pem -pubout -outform der</code>), encodes it in base64 (<code>openssl base64 -A</code>), and finally writes it to the <code>record.txt</code> file.</p>
<p>Copy the contents of <code>record.txt</code> file and add it as a TXT record of <code>mailchannels._domainkey</code>.</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #d8dee9ff">mailchannels._domainkey IN TXT "&#x3C;content of the file record.txt>"</span></span></code></pre>
<p><img src="https://github.com/maggie-j-liu/mail/raw/main/dns.png" alt="Adding the TXT record"></p>
<h2 id="domain-protection">Domain protection</h2>
<p>After this blog post was written, MailChannels has introduced <a href="https://support.mailchannels.com/hc/en-us/articles/16918954360845-Secure-your-domain-name-against-spoofing-with-Domain-Lockdown-" target="_blank" rel="nofollow">Domain Lockdown</a> that is MANDATORY for Cloudflare Workers users… which means yet another DNS record to add.</p>
<p>First you need to obtain your Workers subdomain. Go to "Workers &#x26; Pages" on your Cloudflare account, and in the right sidebar it should be there.</p>
<p><img src="https://share.exerra.xyz/sjXluJ2.png" alt="Cloudflare Workers subdomain"></p>
<p>After that, add a TXT record <code>_mailchannels</code> with the value of <code>v=mc1 cfid=&#x3C;WORKERS SUBDOMAIN></code></p>
<p><strong>Note that it may take a while for this to update - if it doesn't work at first, wait a bit.</strong></p>
<p>Also, you can add as many <code>cfid</code> as you want, so I would also suggest adding one for both your domain and the subdomain of the CF Worker you will be sending email from (not sure if it does something, but just in case).</p>
<p><img src="https://share.exerra.xyz/azfwwKa.png" alt="Domain protection TXT DNS record example"></p>
<hr>
<h1 id="call-the-mailchannels-api">Call the MailChannels API</h1>
<p>MailChannels has created a transactional API for Cloudflare customers. This API can only be ran from Cloudflare IP addresses, so don't even try to run it from your own server (or on local mode).</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #616E88">// src/index.ts</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">interface</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Env</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">  DKIM_PRIVATE_KEY</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">string</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #81A1C1">export</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">default</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">async</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">email</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">message</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">ForwardableEmailMessage</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">env</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Env</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">const</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DKIM_PRIVATE_KEY</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">env</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">emailReq</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">await</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">fetch</span><span style="color: #D8DEE9FF">( </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">https://api.mailchannels.net/tx/v1/send</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">method</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">POST</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">headers</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">content-type</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">application/json</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">body</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">JSON</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">stringify</span><span style="color: #D8DEE9FF">( </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">personalizations</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">to</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [ </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">""</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF">]</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// who to send the email to, add your own recipient</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">dkim_domain</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">[YOUR DOMAIN]</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">dkim_selector</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">mailchannels</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// [selector]._domainkey.yourdomain.com</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">dkim_private_key</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DKIM_PRIVATE_KEY</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #D8DEE9FF">                ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">from</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #D8DEE9">email</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">''</span><span style="color: #D8DEE9FF"> </span><span style="color: #616E88">// add your FROM email here</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">},</span></span>
<span class="line"><span style="color: #ECEFF4">                </span><span style="color: #616E88">//reply_to: { email: email.from.address },</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">subject</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">Subject</span><span style="color: #ECEFF4">"</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">content</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> [</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">type</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">'</span><span style="color: #A3BE8C">text/plain</span><span style="color: #ECEFF4">'</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                        </span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">"</span><span style="color: #A3BE8C">content</span><span style="color: #ECEFF4">"</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">                ]</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> )</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> )</span><span style="color: #81A1C1">;</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre>
<p>More documentation on the transactional API can be found <a href="https://api.mailchannels.net/tx/v1/documentation" target="_blank" rel="nofollow">here</a>.</p>
<hr>
<p>I made a small edit that fixed a major issue, sorry if you had any issues with what I wrote beforehand 😥</p>]]></description><pubDate>Sat, 17 Jun 2023 00:00:00 GMT</pubDate><media_content width="560" url="https://share.exerra.xyz/4PGtCPo.png" /></item><item><title><![CDATA[How to reply to an email in code]]></title><link>https://blog.exerra.xyz/blog/how-to-reply-to-an-email-in-code</link><guid>https://blog.exerra.xyz/blog/how-to-reply-to-an-email-in-code</guid><description><![CDATA[<p>Email replies are an essential part of the email experience, and if you are building a service that requires the sending of email replies (for any reason), keep reading!</p>
<p>Each email sent contains headers with information about return paths, authentication results, provider info, signatures, subject, content type and etc. For replies, <strong>the ones you should care about</strong> are the headers <code>Message-Id</code> and <code>References</code>. <strong>These provide references</strong> for email providers, in order to let them determine to which email you are replying.</p>
<p>To make your email a reply, <strong>just return a <code>References</code> header</strong> with the value of the <code>References</code> header of the email you're replying to and the <code>Message-Id</code> header at the end, <strong>alongside a <code>In-Reply-To</code> header</strong> with the value of the <code>Message-Id</code> header.</p>]]></description><pubDate>Wed, 17 May 2023 00:00:00 GMT</pubDate><media_content width="560" url="https://share.exerra.xyz/sharex/2023/05/lyAaiaGQBO.png" /></item><item><title><![CDATA[Play & mod Skyrim on a Mac without Crossover]]></title><link>https://blog.exerra.xyz/blog/skyrim-on-mac</link><guid>https://blog.exerra.xyz/blog/skyrim-on-mac</guid><description><![CDATA[<p>If you've ever wanted to play Skyrim on a Mac, you've probably run into CrossOver. It is an app that lets you open Windows applications on Mac, and thus has been used by every single article or video showing you how to play Windows games on Mac. The thing is, <strong>CrossOver is not a free app</strong>. It costs $64, which may not seem like a lot, but when you're trying to play a game that you've already purchased, it can be a bit of a bummer to have to fork over even more money.</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1068932701966573699/Screenshot_2023-01-28_at_18.35.42.png" alt="Crossover pricing"></p>
<p>CrossOver is just a wrapper for Wine, which is a free and open-source compatibility layer that allows you to run Windows applications on other operating systems. It essentially acts as a Windows environment on your Mac, making it possible to run games like Skyrim. However, it's important to note that while CrossOver may be a more polished and user-friendly option, there are other alternatives like Porting Kit that can do the same thing, but for free. Porting Kit is less sophisticated than CrossOver, but it does (basically) the same thing, just with a bit less community support.</p>
<p>So, let's install Skyrim (and later mod it) with Porting Kit!</p>
<hr>
<h1 id="requirements">Requirements</h1>
<h2 id="cpu">CPU</h2>
<p>This guide is focused on installing Skyrim on an Apple Silicon computer (aka M1, M2 etc). If you own an Intel Mac, instead of doing this, you should <a href="https://support.apple.com/en-us/HT201468" target="_blank" rel="nofollow">install Windows thru Bootcamp</a> and run Skyrim natively.</p>
<p>If you're not sure on which CPU you have, click the Apple logo in the top-left, then click "About This Mac" and alongside your computers name should be the CPU in brackets. If it says M1, M2 or higher, you have an Apple silicon Mac, otherwise you have an Intel Mac.</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1068937123064983572/Screenshot_2023-01-28_at_18.53.08.png" alt="Instructional image on how to check which CPU you have in the &#x22;About This Mac&#x22; application"></p>
<h2 id="ram">RAM</h2>
<p>While 8 GB of RAM will work, it is <strong>strongly recommended</strong> to play Skyrim on a computer with <strong>at least 16 GB of RAM</strong>. For all SoCs (e.g Apple silicon), RAM is shared with the GPU, but with the more conventional systems (Seperate CPU, GPU, RAM, etc) RAM and VRAM (used only for the GPU) is seperate. Because of that, Skyrim expects <em>at least</em> 4 GB of RAM, plus a decent amount of VRAM. With only 8 GB of unified memory (aka RAM) Skyrim doesn't have enough "juice" to run very smoothly under all of the emulation layers.</p>
<h2 id="disk-space">Disk space</h2>
<p>Skyrim itself is 14 GB, but I would recommend like 20 GB if you do not wish to mod Skyrim, and <em>at least</em> 30 GB if you wish to mod Skyrim (though I would recommend 40 GB).</p>
<p>It is possible to use an external drive for this, but it <strong>must be formated in APFS</strong>.</p>
<h2 id="game">Game</h2>
<p>This guide focuses on the latest <a href="https://store.steampowered.com/app/489830/" target="_blank" rel="nofollow">Skyrim Special Edition</a> version on Steam. If you own the GOG version of the game, that may also work (install steps are a bit different, mostly just selecting the GOG version in Porting Kit &#x26; downloading offline installer before clicking Install in Porting Kit).</p>
<p>If you own the old version of Skyrim (just named "The Elder Scrolls V: Skyrim"), you can try to follow this guide, but how well it will work I do not know. In any case, it is recommended to upgrade to the Special Edition.</p>
<hr>
<h1 id="installing-porting-kit">Installing Porting Kit</h1>
<p>Porting Kit is the software that makes this happen. If you already own CrossOver, skip this section.</p>
<ol>
<li>Download the DMG version of Porting Kit from <a href="https://www.portingkit.com/download" target="_blank" rel="nofollow">here</a>. <strong>Do not download from any other sources</strong>.</li>
<li>Open the DMG and drag Porting Kit into your Applications folder.</li>
<li>Open Porting Kit and follow the initial setup</li>
</ol>
<hr>
<h1 id="installing-skyrim">Installing Skyrim</h1>
<p>Once Porting Kit is installed, navigate to the "Library" tab, then search for Skyrim in the port list. Click on the icon for "The Elder Scrolls V: Skyrim SE" (<strong>DO NOT click the other one</strong>), and then click "Install". It will ask you which game version you want to install (Steam or GOG). For the purposes of this guide, we will be installing the Steam version.</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1068962926523854938/Screenshot_2023-01-28_at_20.36.14.png" alt="Instructional image instructing which icon to click"></p>
<p>Once the installer has appeared, just follow what the installer says &#x26; wait until it downloads everything. If you wish to put the game in a different location, the "Destination Select" stage has a button where you can change the install location. Again, <strong>if you wish to install it on a different drive, make sure it is formatted as an APFS drive</strong>.</p>
<p>Once the installer is completed, go back to Porting Kit, click on Skyrim in the sidebar and launch it. After that, you will need to log into Steam and download Skyrim Special Edition <strong>in the default location</strong>.</p>
<p>After it has finished downloading, just launch the game!</p>
<h2 id="settings">Settings</h2>
<p>When you arrive at the launcher, it will try to detect what settings it should run &#x26; will assign you Ultra settings. If you're playing on M1 or M2 <strong>you cannot play with those settings</strong> due to the massive overhead due to the translation layers.</p>
<p>First set the settings to Low, then click "Advanced" and check that everything in "Detail" is set to lowest &#x26; turned off. Then launch Skyrim, get past the tutorial and if it runs well, start tweaking settings to be higher (everything except view distance will need to be configured in the launcher).</p>
<p>If you wish to mod Skyrim, <strong>it is important that you do this step first</strong>, as most popular modding tools create a virtual file system that they use, and after that point the changes in the launcher won't affect the game at all (if you're launching Skyrim thru the mod manager).</p>
<hr>
<h1 id="modding-skyrim">Modding Skyrim</h1>
<p>There are multiple ways to mod Skyrim if you're on the Steam version. You could either use the built-in mod downloader, or download &#x26; install mods yourself with a mod manager. Granted, you could also just drag the files into the filesystem manually, but that presents large issues with safety. Most modern mod managers (e.g Mod Organizer 2 &#x26; Vortex) use virtual filesystems in order to not mess up your install of the game. With virtual filesystems, your mods are stored in a different folder than your game installation (and each mod is stored in a seperate folder even), and thus let you easily add/remove/toggle mods without messing anything up.</p>
<p>Another reason to go with a mod manager is that the GOG version of the game has been stripped of <em>any</em> DRM. That means that there is no Creation Club (basically paid mods) and no built-in mod downloader.</p>
<p>If you wish to mod Skyrim only with the built-in mod downloader, you can stop reading. From now on, everything will be focused only on how to mod Skyrim with a mod manager.</p>
<p>To follow this guide you will need a Nexus Mods account. If you do not already have one, go <a href="https://users.nexusmods.com/register" target="_blank" rel="nofollow">here</a> and create one.</p>
<h2 id="installing-skse">Installing SKSE</h2>
<p>This step is tehnically optional, but strongly recommended. SKSE is a script extender for Skyrim and is a required dependency of <em>many</em> mods.</p>
<p>First, go to the page for <a href="https://theunarchiver.com/" target="_blank" rel="nofollow">The Unarchiver</a>, then download, install and open it (you can close it later). <strong>This app is required to extract 7z files</strong>.</p>
<p>When that is done, navigate to the <a href="https://skse.silverlock.org/" target="_blank" rel="nofollow">official page</a> for SKSE. Then look for the appropriate version for your install (Anniversary Edition for Steam, GOG Anniversary Edition build for GOG) and download it. While it is downloading, navigate to the place where you installed Skyrim on your Mac, right click it and press "Show Package Contents", open the <code>drive_c</code> folder and find your Skyrim install. For Steam users, it should be in <code>Program Files (x86)/Steam/steamapps/common/Skyrim Special Edition/</code>. Make sure that folder contains <code>SkyrimSE.exe</code>.</p>
<p>After that, open a new tab (or window) in Finder (the MacOS file manager), go to your Downloads folder and open the downloaded 7z file with The Unarchiver. Then, open the newly created folder, copy the <code>Data</code> folder alongside all DLL and EXE files (hold down ⌘ to be able to select multiple files), and paste it into the folder where <code>SkyrimSE.exe</code> is located.</p>
<p>And now SKSE is installed!</p>
<h2 id="installing-mod-organizer-2">Installing Mod Organizer 2</h2>
<p><img src="https://techphobos.com/wp-content/uploads/2022/02/Mod-Organizer.png" alt="Mod Organizer 2 logo"></p>
<p>Mod Organizer 2 is one of those mod managers I talked about. As of writing, Mod Organizer 2 is the best mod manager for a lot of games (notably the Fallout and Elder Scrolls series), and, fortunately, works well thru Wine.</p>
<p>First, go to its <a href="https://www.nexusmods.com/skyrimspecialedition/mods/6194?tab=files" target="_blank" rel="nofollow">Nexus Mods page</a> and download the latest main file. When that is done, go to Porting Kit, right click Skyrim in the sidebar, hover over "Advanced Tools" and click "Launch Wineskin app". <strong>Make sure all wine processes are closed</strong> (you can go to Porting Kit, right click Skyrim and press "Force close" to make sure).</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1068980719587696793/Screenshot_2023-01-28_at_21.47.37.png" alt="Instructional image showing a right-click menu with &#x22;Launch Wineskin app&#x22; being highlighted"></p>
<p>In the Wineskin app, click "Install software", then click "Choose Setup Executable" and select the Mod Organizer 2 setup EXE in the file picker.</p>
<p>When that is done, right click Skyrim again, hover over "Advanced Tools" and press "Add shortcut to EXE". That will open up a new window where you can write the name for the shortcut (just name it "Mod Organizer 2") and afterwards a file picker where you have to select the EXE. If you didn't change the install path, it should be <code>Modding/MO2/ModOrganizer.exe</code>.</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1068983670771306506/Screenshot_2023-01-28_at_21.59.20.png" alt="Instructional image showing a right-click menu with &#x22;Add shortcut to EXE&#x22; being highlighted"></p>
<p>When that is done, launch Mod Organizer 2 through the shortcut you just made and follow the setup. If it doesn't launch, restart your Mac. Also, if it throws a warning about not being able to load a plugin, either click ignore, or blacklist the plugin.</p>
<p>Double-check that Mod Organizer 2 has detected all of the launchable EXEs. It always detects Skyrim Special Edition and the Launcher, but sometimes it fails to detect SKSE. If it hasn't detected SKSE, click "&#x3C;Edit...>" and create a new empty executable with this path <code>C:\Program Files (x86)\Steam\steamapps\common\Skyrim Special Edition\skse64_loader.exe</code></p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1069006392956043324/image.png" alt="Instructional image highlighting required launchable EXEs in Mod Organizer 2"></p>
<h2 id="installing-mods">Installing mods</h2>
<p>With Mod Organizer 2 set up, all that is left is actually installing the mods. First, you need to locate mods. A great website where you can find mods for games is <a href="https://www.nexusmods.com/skyrimspecialedition" target="_blank" rel="nofollow">Nexus Mods</a> (don't browse it while family is around, the Skyrim community is really horny...). If we were on Windows, we could just click the "Mod manager download" button in Nexus Mods, but because Mod Organizer 2 is run thru wine, the browser has no way to communicate with it, so click "Manual download" on a mod you want.</p>
<p>Once you have downloaded a mod, I would recommend putting it in the Documents folder for easy access (the wine file explorer has the Documents folder bookmarked). After that, go to Mod Organizer 2, and click the icon with a Disc at the top left. That will open a file picker, where you can quickly go to Documents and select the mod archive.</p>
<p><img src="https://cdn.discordapp.com/attachments/713134823706984564/1069007139152085113/image.png" alt="Instructional image highlighting the button needed to install a mod in Mod Organizer 2"></p>
<p>When a mod is installed, make sure to enable it by toggling the checkbox next to its name.</p>
<h2 id="installing-an-enb">Installing an ENB</h2>
<p><strong>Warning: Only use an ENB if you have really good performance in Skyrim.</strong></p>
<p>Installing an ENB is a bit more of a hands-on process.</p>
<p>First, download the latest version of the ENB engine from <a href="http://enbdev.com/download_mod_tesskyrimse.html" target="_blank" rel="nofollow">the official website</a>.</p>
<p>Then, unzip it, go into the unzipped folder and open the "LinuxVersion" folder. That folder contains a DLL that you have to paste into the Skyrim installation folder. In case you have forgot how to go there, refer back to the <a href="#installing-skse">Installing SKSE</a> section.</p>
<p>After that, locate an ENB on Nexus Mods, download it and follow the instructions in the README (if there are any). If there are no instructions, just copy the following items into the Skyrim installation folder: <code>enbcache</code>, <code>enblocal.ini</code>, <code>enbseries</code>, <code>enbseries.ini</code>.</p>
<h2 id="mod-incompatibility">Mod incompatibility</h2>
<p>I have set up a document where I will track mod incompatibility on Apple silicon. You can view it <a href="https://notea.exerra.xyz/Rl52zpCaJw" target="_blank" rel="nofollow">here</a>. It will most likely not get updated often, as I do not mod Skyrim so often where I can find a lot of incompatible mods.</p>
<hr>
<h1 id="creating-mods">Creating mods</h1>
<p>It is possible to create mods for Skyrim on a Mac using the Creation Kit! I do advise you to first follow the <a href="#modding-skyrim">"Modding Skyrim"</a> section as the experience will be better with it.</p>
<h2 id="issues">Issues</h2>
<p>It doesn't work fully, though; most stuff works, but the toolbar does not show up on Mac.</p>
<p>Windows:
<img src="https://cdn.discordapp.com/attachments/713134823706984564/1071510450321235968/Screenshot_2023-02-04_at_21.19.51.png" alt=""></p>
<p>Mac:
<img src="https://cdn.discordapp.com/attachments/713134823706984564/1071510511453216778/Screenshot_2023-02-04_at_21.20.09.png" alt=""></p>
<p>I'm not sure how bad that is, pretty sure some stuff is available in the menus anyway, but I wouldn't count on the experience being even close to the Windows one.</p>
<h2 id="installing-the-creation-kit">Installing the Creation Kit</h2>
<p>Whether you have an ambitious mod idea, a simple fix idea or just want to fool around, you'll need to install the Creation Kit first. You can download the Creation Kit directly to your game folder from Steam (first, add it to your library <a href="https://store.steampowered.com/app/1946180/" target="_blank" rel="nofollow">here</a>). I'm not sure about the GOG version, but I assume it either comes bundled-in or is downloadable from the extras.</p>
<p>After that, launch it and it should work basically out-of-the-box. If it does not launch, restart your computer.</p>
<h2 id="using-the-creation-kit">Using the Creation Kit</h2>
<p>If you don't know how to use the Creation Kit, you can follow the tutorials <a href="https://www.creationkit.com/index.php?title=Category:Getting_Started" target="_blank" rel="nofollow">here</a>.</p>
<p>It is possible to use the Creation Kit through Steam or through a mod manager. If you wish to use the Creation Kit through Steam, which is recommended for mods that don't have external dependencies, all you have to do is click Play on Steam.</p>
<p>If you wish to create mods with external dependencies, you have to launch the Creation Kit through a mod manager. Assuming that you chose Mod Organizer 2, all you have to do is close it (if it was open) and open it again. MO2 should automatically see the Creation Kit and add it to the launchable EXE dropdown.</p>
<p>To test a mod, all you have to do is launch Skyrim through Mod Organiser 2 with the mod enabled.</p>
<h2 id="publishing-mods">Publishing mods</h2>
<p>First you have to save the file as as an ESP with the name of your mod.</p>
<p>Then you have to extract it. If you launched the Creation Kit through Steam, it will be in the <code>Program Files (x86)/Steam/steamapps/common/Skyrim Special Edition/Data/</code> directory (if you're unsure on how to get there using Finder, refer to the <a href="#installing-skse">"Installing SKSE"</a> section). If you launched it through Mod Organizer 2, it should be somewhere in <code>users/&#x3C;your_user>/AppData/Local/ModOrganizer/Skyrim Special Edition/mods/</code> (though I might be wrong, I haven't worked with MO2 + CK in a while).</p>
<p>And now comes the fun part - publishing the mod on Nexus Mods. You could distribute the mod on different websites (and it is actually preferred for a lot of NSFW mods), but Nexus Mods is by far the most popular website where people get mods, and thus will probably be the best choice for your mod.</p>
<p>It is actually quite simple, all you have to do is go <a href="https://www.nexusmods.com/mods/add" target="_blank" rel="nofollow">here</a> while logged in, fill out the form and boom you're a mod author! Also if you ever reach like 1000 unique downloads (if my memory is correct) and you join the Nexus Mods Mod Author discord server, make sure to ping me! 👀</p>
<hr>
<h1 id="a-few-notes">A few notes</h1>
<p>The team behind <a href="https://asahilinux.org/" target="_blank" rel="nofollow">Asahi Linux</a> recently improved their GPU drivers to be faster than Apple’s (in some cases). Currently they are experimenting with Proton, a Windows app emulation software by Valve. If they succeed, all of this could become irrelevant. If they do succeed in running Proton, I will update this blog post with a new section about running it under <a href="https://asahilinux.org/" target="_blank" rel="nofollow">Asahi Linux</a>.</p>
<hr>
<p>And this is the end. If you have any questions or feedback, please reach out to me. You can find my contacts in <a href="https://exerra.xyz" target="_blank" rel="nofollow">my website</a>.</p>
<p>If I find any issues, I will update this post to warn about it, and if it is fixable I will also include a guide on how to fix it.</p>]]></description><pubDate>Sun, 29 Jan 2023 00:00:00 GMT</pubDate></item><item><title><![CDATA[Arc is worse than advertised]]></title><link>https://blog.exerra.xyz/blog/arc-analytics</link><guid>https://blog.exerra.xyz/blog/arc-analytics</guid><description><![CDATA[<p>Arc is a browser by the startup named "The Browser Company". It claims to be a different, better, browsing experience. The idea is unique in comparison to what Google, Mozilla and Apple is doing with their browsers, but it isn't anything revolutionary, and some browsers have already implemented certain features of Arc long ago.</p>
<p>If Arc was just what it is at face value, I wouldn't be writing a blog post. So far from what I've seen nobody is talking about this, so I feel obligated to write an analysis on Arc by Browser company.</p>
<p>In order to understand what Arc is about and be able to check network logs, all of the research was done on Arc.</p>
<hr>
<h1 id="arcs-gimmick">Arc's gimmick</h1>
<p>The Browser Company invisions Arc as a revolution; as a browser that will change the way you use the internet. Arc does have unique features that other browsers don't, such as:</p>
<ul>
<li>A command bar</li>
<li>Split view</li>
<li>Boosts (extension maker for non-coders)</li>
<li>Notes</li>
<li>A whiteboard feature</li>
<li>Previews on pinned tabs (only have gotten it to work with Google Calendar though)</li>
</ul>
<p>While these features are nice-to-haves, I do not believe that they are must-haves. If you become proficient with them, sure they can be must-haves, but using the browser is weird at first and you have to re-learn how to browse the web (large friction points are never good). And there are a few features which already exist on some browsers, but not others. For example: Spaces.</p>
<p>Spaces is an organisational tool which lets you split your tabs, pinned tabs, bookmarks and notes into seperate screens. Normally this would work with user profiles and multiple windows on most of the other browsers, but Safari already has such a feature, and considering that Arc is (currently) only available on macOS, you have to wonder if the high friction point and loss of some Apple ecosystem exclusive features is worth it.</p>
<hr>
<h1 id="the-bad-side-of-arc">The bad side of Arc</h1>
<p>Arc, like many apps, sends analytics. Normally that isn't an issue, but in Arc's case a number of factors make this, in my opinion, pretty shady.</p>
<p>While poking around in my proxy with Arc open, I noticed the analytics. I thought nothing of it until I saw just how frequently it got sent, so I decided to investigate. Arc features analytics by Segment and monitoring by Sentry.</p>
<p>Sentry is a performance and error monitoring service. It is built for developers to monitor any issues with their code, and is nothing to worry about. Normally it doesn't send anything unless an error has occured.</p>
<p>This is where it gets shady. Segment is a customer data platform by Twilio. It can build a profile on you across all platforms and channels and is most often used to deliver personalised experiences (mostly in commerce). There are plenty of analytics services that anonymise data and don't track users, but Segment is not one of them. Each request that gets sent includes a unique user ID for each user, so they can see exactly how <strong>you</strong> are using a service. Furthermore, the analytics that Arc sends includes the screen size, OS, locale, device (type, model, manufacturer), timezone and network connectivity.</p>
<p>Every time Arc is put into the foreground/background, it sends an analytic. Every time you open a URL, it sends an analytic. Hovering over the top bar? Arc sends an analytic. Dismissing a pop-up that Arc throws at you? Also sends an analytic. Most of what you do sends over an analytic to Segment's servers, where it then can be routed to different Analytics services &#x26; be used to build a profile of how <strong>you</strong> use Arc and what devices you own.</p>
<p><img src="https://share.exerra.xyz/pMT4mpV.png" alt="Network logs of the analytics"></p>
<p>Let's not pretend that this isn't common, though. The most popular browser currently (and on whose engine Arc is based on) sends web usage data to Google, which includes the URLs you go to and more, but Arc doesn't send the URL you have visited. At this point you must be wondering why Arc sending analytics data is so blasphemous when Google sends <em>more</em> data. Well, you can disable the Google Web Usage data collection in your Google account settings, but you <em>cannot</em> disable Arc analytics, which also build up a profile on you that can be used to target ads (Segment is actually built for that, from what I can see). Google also lets you export your data easily and check what data it has collected on you, but Arc has no such feature. Furthermore with Google there is only one privacy policy, but with Segment that data can be routed to many different services, each with their own privacy policies that <strong>can change</strong>. Remember, if a tech company starts out with a strong privacy policy, that doesn't mean that they will never sell your data, as privacy policies can <strong>always</strong> change to allow more and more data harvesting and selling. Currently the privacy policy for The Browser Company seems to be fine, but you can never trust tech companies.</p>
<hr>
<h1 id="in-summary">In summary...</h1>
<p>Arc is an app that sends analytics data to Segment, a customer data platform by Twilio, and Sentry, a performance and error monitoring service. Segment's analytics service is not anonymous and includes a unique user ID for each user, as well as information about their device and usage of the app. This data is used to build profiles of users' app usage and can be routed to other analytics services for targeted advertising. Google's Chrome browser also collects data on users' web usage, but this data collection can be disabled in the user's account settings and the collected data can be exported and reviewed. In contrast, Arc does not have a feature to disable analytics collection or review collected data, and the data collected by Segment can be routed to multiple services with potentially changing privacy policies.</p>
<p>If you are fine with that level of analytics, Arc is a great (but hard to get into) browser with interesting features that you could find useful, not to mention the unique (and nice) style. But if you are concerned about your privacy and the potential for your data to be used for targeted advertising, you may want to consider using a different browser that offers more control over data collection and usage.</p>]]></description><pubDate>Sun, 08 Jan 2023 00:00:00 GMT</pubDate><media_content width="560" url="https://share.exerra.xyz/QGO2Cl2.png" /></item><item><title><![CDATA[2022 was a good year]]></title><link>https://blog.exerra.xyz/blog/2022-was-a-good-year</link><guid>https://blog.exerra.xyz/blog/2022-was-a-good-year</guid><description><![CDATA[<p>New year, new blog update. To be honest, I was waiting for more stuff to share before writing a blog update, but since it is the new year I decided to release it anyway.</p>
<p>2022 has been an amazing year for me, and I hope 2023 is even better. I've made a bunch of amazing projects, and I have won something (read on to find out where). It hasn't been without it's downsides, for example I caught Covid-19 in september, and that was horrible, but overall it has been great (except Apple releasing AirPods Pro gen 2 a few months after I get the gen 1 :c).</p>
<h1 id="exerra-api-v2">Exerra API v2</h1>
<p>I have spent a while remaking the Exerra API. It is redesigned from the ground up to be more modular, secure and user-friendly. The original API wasn't very modular; everything was in one file (as it started in very humble roots) and thus was a nightmare to develop in. Alongside that, due to the nature of the API, it was saving a lot of files which meant it wasn't really git-ready. And to top it all off, it had Basic auth meaning it was insecure and wasn't really built to handle a dynamic user system.</p>
<p>... v2 changes <em>all</em> of that. Starting with the core structure - it has been built to be as modular as it can be, <em>and</em> it doesn't store any files which lets it be nicely stored on git. Auth has been built using the Identity backbone, which let me implement OAuth2 (more info about how it works later). Some small extras include: a single response system for the entire API, improvements to the Phishing API, powerful validation and more.</p>
<h2 id="oauth2">OAuth2</h2>
<p>By using the Identity system, it is possible to create OAuth2 that lets users interact with the API in a safe way. Identity has been expanded to include another record: auth. In it, there will be the users OAuth2 secret, which when coupled with the users ID can be used to generate OAuth2 tokens. With a token you won't be able to access everything though, as the auth record also will contain scopes the user has access to, and if the user doesn't have a scope needed for a route, then the request will be denied with a 401. Alongside that, if you want to have some particular scopes (provided you have access to them), you can include them in the "scopes" field when creating an OAuth2 token, which in turn will create a token with only the scopes you've asked for.</p>
<p>This system will satisfy everyone and let users interact with the Exerra API in ways they couldn't before.</p>
<h2 id="response-system">Response system</h2>
<p>A large problem v1 of the API had, was that there was no single response system. Every endpoint could spit out data in its own structure. With v2 of the API, that is no more. Every single route (except the OAuth2 token issuer route) uses the same custom response function, which formats the data in this schema:</p>
<pre is:raw="" class="astro-code" style="background-color: #2e3440ff; overflow-x: auto;"><code><span class="line"><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    status</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">HTTPStatusCode</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">    data</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">object</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre>
<p>I know, it isn't much, but when writing applications that use the API, a unified response system goes a long way in ensuring a good development experience (DX).</p>
<h2 id="validation">Validation</h2>
<p>With v2 of the API, a clear focus has been set on ensuring safety. Whether it be Auth safety, or parameter safety, v2 has been built with that in mind. And so because of that, v2 of the API features strong validation of the query parameters, route parameters and body for endpoints that need it. If you have made a mistake, the API will return an error clearly stating what has went wrong. I hope the DX improvement this has is immeasurable (not only for you, but also for me 😅).</p>
<h2 id="phishing-api">Phishing API</h2>
<p>The original API was... fine. It certainly wasn't bad, but developing in it wasn't nice and it was lacking crucial features. For example, v1 of the API only grabbed domains, and it saved those in a JSON file. V2 of the API grabs both domains and URLs, and it saves them in memory, which should improve speeds + make the API more git-friendly. Along with that, the "Get all" endpoint has been modified a bit to allow you to grab either all domains or all links (I seperated them because both of them are >14 MB in size), and the "Check a URL" endpoint has been modified to check against URLs first, and then domains. The response for "Check a URL" also indicates whether the URL was checked against URLs or domains. API docs for v2 are coming soon, when they are done I will update this blog post with a URL to them.</p>
<p>And the best part? V2 of the Phishing API brings 130k more flagged domains, and, of course, also includes the <strong>830k!!</strong> new flagged URLs.</p>
<h2 id="identity-api">Identity API</h2>
<p>The Identity API is now live! It isn't complete, but it does have the most necessary endpoints for 3rd party users. Currently finished endpoints include:</p>
<ul>
<li>Get self (Uses OAuth2 to return a (nearly complete) database record of you)</li>
<li>Get user by ID (Public endpoint for getting info about an Identity user by their Identity ID. The response contains their ID, username, profile data, socials and picture)</li>
<li>Find user by platform (OAuth2-protected endpoint for finding a user by a platform and their ID (only works if the user has linked that platform to their account))</li>
<li>Patch profile (Uses OAuth2 to update the profile record of you)</li>
</ul>
<p>API docs are coming soon. More info down below.</p>
<h2 id="api-docs">API docs</h2>
<p>A crucial part of every API are the docs, and sadly the release of v2 doesn't come with them. I will be working on the docs soon, I first have to fully finish the API with mostly QOL features like:</p>
<ul>
<li>each module of the API defining which env variables they need, and if they aren't provided then not loading those modules</li>
<li>improved error handling (if a module cannot load, the API cannot load)</li>
<li>speed improvements (the speed is better than v1, but I imagine there is still more stuff to be done)</li>
</ul>
<p>When all of that is finished, I will focus more on the API docs, which should help a ton with developers adopting the API. If you want to use it now, and have any questions, go to <a href="https://exerra.xyz" target="_blank" rel="nofollow">my website</a> as it contains social links where you can contact me (Discord is recommended).</p>
<hr>
<h1 id="shortener">Shortener</h1>
<p><img src="https://cdn.exerra.xyz/png/mockups/exerra-shortener-1x.png" alt="A mockup image of Exerra Shortener"></p>
<p>After the last blog update, I have launched <a href="https://s.exerra.xyz" target="_blank" rel="nofollow">Exerra Shortener</a>. It is a URL shortener that incorporates the Phishing API for unparalleled phishing protection. Alongside that, it is one of the many (upcoming) projects in the Identity ecosystem. Sadly, the dashboard for Identity isn't done yet, but you can already sign up for an Identity account in any project using Identity.</p>
<p>I am looking for beta-testers, so if you want to help test Exerra Shortener (or any of my projects), then please go to <a href="https://exerra.xyz" target="_blank" rel="nofollow">my website</a> and contact me on any of the platforms listed there.</p>
<hr>
<h1 id="cdn">CDN</h1>
<p>I have added Twemoji to the CDN. If you want to use Twemoji without self-hosting it on your own CDN or using the Twemoji JS library, you can use my CDN alongside my new <a href="https://npmjs.com/package/extract-unicode" target="_blank" rel="nofollow">extract-unicode</a> package. It extracts all unicode codepoints for a given symbol, which is perfect for custom emoji libraries.</p>
<p>I would suggest using Twemoji for flags, as Windows 11 has completely removed country flags from its emojis, which leads to flags appearing as the country code (which is why you can see a Twemoji flag on my blog and my website).</p>
<hr>
<h1 id="blog">Blog</h1>
<p>There is a new website for the blog! The file paths have changed a bit, sorry about that, but the new version features a sitemap, RSS and a nicer design! + it is built with Astro 🚀</p>
<p>The RSS feed puts the parsed HTML code in the description part, so you don't have to leave the RSS reader!</p>
<hr>
<h1 id="benchmarks">Benchmarks</h1>
<p>Unfortunately, I haven't had time to benchmark a lot of games, so the <a href="https://benchmarks.exerra.xyz" target="_blank" rel="nofollow">Benchmarks project</a> has kind of died. I plan on soon giving it a fresh coat of paint (remake the design), and add info about how games run on the Apple M1 as well! I have been playing a few games on my M1 mac, like The Witcher 3: Wild Hunt and some Nintendo Switch games thru Ryujinx. I haven't yet uploaded the benchmarks as I havent updated to macOS Ventura, which features an FPS counter for games running on Metal. Once I upgrade to macOS Ventura, I will have the ability to write benchmarks for a bunch of games for Apple M1.</p>
<hr>
<h1 id="personal-news">Personal news</h1>
<p>My blog up until now has been mostly acting as a DevLog, but I want to change things up a bit. In the <a href="/blog/i-should-start-asking-for-financial-compensation">previous update</a> I mentioned that I want to start writing more technological articles which critique a technology, point out their flaws and upsides. That hasn't changed, but, sadly, I haven't yet figured out what to write about. I have a few ideas, but I haven't been able to come up with a solid article for them. What I haven't mentioned, though, is that I want to start writing more about my personal life, but my life is kinda boring and currently I want to stay a bit anonymous, so that is kind of hard to do.</p>
<p>There are a few news which aren't linked to me, or which can be anonymised a bit, so here we go.</p>
<h2 id="iphone-14">iPhone 14</h2>
<p><img src="https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/iphone-14-finish-select-202209-6-1inch-purple_AV1_FMT_WHH?wid=1280&#x26;hei=492&#x26;fmt=p-jpg&#x26;qlt=80&#x26;.v=1661026908259" alt="An image of the Apple iPhone 14 in purple"></p>
<p>I have gotten a new phone! I upgraded from the royal blue iPhone 12 to the purple iPhone 14 and, to be honest, the difference is underwhelming, yet nice.</p>
<p>The battery on my old phone already had started to degrade, and with my (usually) long days the better battery in the iPhone 14 is great. And the colour? Much better. When the iPhone 12 released I grabbed the royal blue version, which was quite nice... until the lavander colour dropped. I wanted it so bad as it is my favourite colour, but I already had bought the royal blue one. With the iPhone 14, I finally got the chance to get a purple phone, and while the colour on the 14 is a very white-ish purple, it is still a shade of purple.</p>
<p>The notch took some time to get used to, but in the end I preferred the smaller notch and the bolder time + connectivity icons. I read about how the notch cut into the content on the newer iPhones, but to be honest on the regular version the notch cuts in by like 1px, which is barely noticeable (I had to LOOK for it). My friends on larger models, though, are saying that it cuts in way more on a larger screen, so if you're worried about that, then pick the regular model.</p>
<p>I haven't yet had the time to test the cameras, as I have been sick a lot lately, but once I get a chance I will test it and include the results in a new blog post (or update this one, who knows). I mainly want to test the action mode, the cinematic mode and the filters. I've heard that cinematic mode has improved a bunch, so I am excited to test that one out.</p>
<p>Connectivity is a strange one. For some reason my iPhone 14 likes to switch to 3G, even when there is an LTE signal. I don't know why that is happening, but I didn't have that issue on my iPhone 12. I hope this is just some weird software issue, and will wait until the next iOS update to truly judge if it's a product defect or a software issue.</p>
<p>Overall, it isn't a large upgrade, but it is a solid update. Not much has changed, but the features that have been here have been polished and improved, which is something I like more than Android. A lot of Android phones add a bunch of gimmicks that aren't fully fleshed-out and get abandoned after a year or two, with Apple it is overall much more underwhelming, but the features that already have been added get perfected, and if there is a new feature then you can be sure that it will last and only get better.</p>
<h2 id="hackathon">Hackathon</h2>
<p>For anonymity purposes I won't reveal which hackathon it was, but I have won first place in a nation-wide hackathon! 🥳</p>
<p>The prize sum was definitely nice, and I've already bought a few tech products with it. I haven't yet gotten a Raspberry Pi, which was one of the things I wanted to indulge in, but the prices have been actually insane! Old models are selling for 100 €, and newer models aren't in a better state! 130 € for a 2gb version of the Raspberry Pi 4 Model B is insane, and the larger RAM models are even more expensive. Sadly, an Arduino isn't going to cut it for me, so I am now waiting for a good deal, which seems to only exist on eBay (not even in local stores 😕).</p>
<p>Nevertheless, I have bought a few goodies and have saved the rest for a good deal on a Raspberry Pi and some more items I am currently checking out (like a desk mat! I am torn between leather and the other material, so if you have a preference please tweet at me!).</p>
<hr>
<p>And with that, the blog post comes to a close. I hope that 2023 can bring great projects to you all, and I may even start making SaaS projects (something I've been putting off for a while). I wish you all a happy 2023.</p>]]></description><pubDate>Tue, 03 Jan 2023 00:00:00 GMT</pubDate><media_content width="560" url="http://cdn.exerra.xyz/jpg/new-years-2022:2023-riga.jpeg" /></item><item><title><![CDATA[Projects on the horizon]]></title><link>https://blog.exerra.xyz/blog/i-should-start-asking-for-financial-compensation</link><guid>https://blog.exerra.xyz/blog/i-should-start-asking-for-financial-compensation</guid><description><![CDATA[<p>It's been 2 months since the last update, not much has happened, but there are a few updates and new pieces of info.</p>
<h1 id="benchmarks">Benchmarks</h1>
<p>For the past few weeks, I have been building a platform for benchmarks 📊. The platform focuses on GPU benchmarks for games, with integrated GPUs being the main focus as they <em>often</em> are underrepresented and need the benchmarks more than dedicated GPUs. It is already live and you can check it out by <a href="https://benchmarks.exerra.xyz" target="_blank" rel="nofollow">clicking here</a>!</p>
<h2 id="how-it-works">How it works</h2>
<p>The obvious choice would be to create a statically generated website with frameworks like Hugo or even NextJS (hey, that's what the blog runs on!), but that would mean that I have to re-build the website every time content changes; which isn't necessarily bad, but if Cloudflare ever decides to add build minutes for Pages, I would be kinda screwed.</p>
<p>So what did I do? I made the website with <strong>Remix &#x26; TailwindCSS</strong> (which is what I have been using for everything lately, gotta 💜 Remix &#x26; TailwindCSS) and the "backend" is <strong>AWS S3</strong>. S3 offers a great API for listing, which is critical for the search feature and I can add new benchmarks <em>without</em> rebuilding the entire website (which can start becoming quite long with large sites).</p>
<h2 id="3rd-party-benchmark-support">3rd party benchmark support</h2>
<p>I am thinking of letting people submit their own benchmarks, but currently I do <strong>not</strong> want to deal with validating that data  so instead if people want me to cover a certain game they can send an email (exerra@exerra.xyz).</p>
<hr>
<h1 id="identity">Identity</h1>
<p>I am actively working on a new identity system for all of my projects. It is not done yet, but I am almost finished writing the code for it and I hope to release it within the next 2 months (yes, it is quite long, but we are talking about an identity system here 😅). I have made sure to make the system flexible so I can slap on new projects willy-nilly.</p>
<p>As this is a sensitive project, I cannot elaborate that much on what is going on in the background as that will expose the project to attacks, but I will tell you about the surface-level stuff and be transparent about the data stored.</p>
<h2 id="data-stored">Data stored</h2>
<p>To make this make sense, I will have to introduce <strong>2 new terms: "Fixed data" &#x26; "Dynamic data".</strong> Fixed data is key/value storage where <strong>the fields cannot be removed</strong> (such as user info, username, socials, etc). To be clear, the value of those fields can be changed or even set to "", but the field itself cannot be deleted. Dynamic data is data that gets generated on-demand and <strong>the fields can be safely removed</strong> without breaking anything (such as info from various services, etc).</p>
<p>With that out of the way, let me talk about the data stored.</p>
<h3 id="user-info-fixed">User info (Fixed)</h3>
<p>In this category, Identity will store information about the users' username, profile picture, legal name, country, email, and <em>possibly</em> phone number (it is there in case I need it, but currently I have no plans on using it so it will not be required).</p>
<h3 id="socials-fixed">Socials (Fixed)</h3>
<p>In this category, Identity will store information about the users' socials. None of the fields are required, but if you try to use a service that is associated with a social (for example Karen Bot dashboard which is associated with Discord), you will need to link that social to your account in order to use the service.</p>
<h3 id="services-dynamic">Services (Dynamic)</h3>
<p>So this is where I can give a less detailed explanation due to the nature of the data. This is where services can add their own data for whatever purpose they might have. There are a few shared fields that I can elaborate on, but the rest is entirely up to the service.</p>
<p>The shared fields are metadata about when the user has first accessed the service, when they have last accessed the service and, lastly, if they even have used the service (more of a fail-safe since there shouldn't be any data on a service if it hasn't been used)</p>
<p>The dynamic nature of this category will really help me create better websites with a centralised identity service while still letting me be flexible on the data, which is the main reason why I went with this approach.</p>
<h2 id="data-removal">Data removal</h2>
<p>As I live in the European Union, I am subject to its data protection laws and therefore have to have a way to fully remove the data stored. Even if I wasn't subject to those laws, I would still offer a way to do it, but since I am subject to those laws you can rest assured knowing that such functionality will not be removed.</p>
<p>As of writing, there is no way to fully remove the data stored, but it is in the pipeline and will be there when Identity is going to be available to the public.</p>
<h2 id="how-it-works-1">How it works</h2>
<p>As I said before, I cannot talk much about what is going on in the background, but I can say that it uses Auth0 for the actual login/signup and user management.</p>
<hr>
<h1 id="cdn">CDN</h1>
<p>In the previous blog post, I mentioned how I was going to move to S3. This is the update to that.</p>
<h2 id="what-has-happened">What has happened</h2>
<p>After I published the blog post, I created a bucket in S3, moved all the files over to there, and started trying to make it work. At first, it didn't quite succeed, and the way S3 works is that I had to change the DNS records to S3 for it to even have a chance to work, so for a bit of time the CDN was down on 2022-08-18.</p>
<p>Eventually, I got everything figured out and it worked, but I had an issue where I no longer could browse the files as before, so I added <a href="https://github.com/qoomon/aws-s3-bucket-browser" target="_blank" rel="nofollow">AWS S3 Bucket Browser by qoomon.</a> I had to change a few settings, so the CDN was down on 2022-08-19 for a few minutes as well.</p>
<p>I am sorry about the CDN being down, after this the CDN should have a >99,9% uptime.</p>
<h2 id="before-the-move">Before the move</h2>
<p>Before I moved to S3, the "CDN" was an express server using the serve-index package to serve files. Because of that, the reliability wasn't that great as I was hosting it on a very cheap VPS with 1GB of RAM and a 1 core shared CPU. I did that when I had a decent VPS and didn't need the CDN beyond my profile picture and a few other things, but now with it growing in scale and <em>needing</em> the uptime, I have finally made the switch to S3. Best part? My API that was hosted on the same VPS is now more stable as well, and the CDN costs me like 0,25 € a month!</p>
<h1 id="future-plans">Future plans</h1>
<p>And with this blog post coming to an end, I want to just talk a bit about what I have planned for the future.</p>
<p>In terms of projects, I plan on finally creating the Karen Bot dashboard. The main reason why I haven't done it yet is simply that I had an idea for the Identity system and didn't want to create it before the Identity system. I also plan to create new bots and start separating from the Karen Bot umbrella as the bot has become too old for me to update. I could redesign Karen Bot, but I have no real motivation and time to do it.</p>
<p>Another idea I have been exploring is paid APIs. I know, I know, they kinda suck from a development POV, but infrastructure costs money and I need to start subsidising the projects I am hosting right now.</p>
<p>Beyond projects, I want to start creating blog posts about languages, technologies (<a href="https://t3.gg/blog/post/types-and-nextjs" target="_blank" rel="nofollow">like this example</a>) and other more technical subjects. I also want to start making guides, which could go either on the blog or <a href="https://docs.exerra.xyz/" target="_blank" rel="nofollow">my docs</a> (which is more likely). I could also start tinkering with real hardware, which seems like it would be a really fun side hobby that I could document on my blog 😄.</p>
<p>P.S: I totally didn't write this blog post on the same day my benchmark website released just so google could see it and display it on search results 👀👀👀</p>]]></description><pubDate>Tue, 04 Oct 2022 00:00:00 GMT</pubDate><media_content width="560" url="https://blog.exerra.xyz/assets/blog/casual-life-3d-workspace.webp" /></item><item><title><![CDATA[I've been quite busy]]></title><link>https://blog.exerra.xyz/blog/jobanirot-bla-celis-nu-gan-ahujela</link><guid>https://blog.exerra.xyz/blog/jobanirot-bla-celis-nu-gan-ahujela</guid><description><![CDATA[<p>It has been quite a while since the last update, and of course a bunch has happened.</p>
<h1 id="react-fingerprint">react-fingerprint</h1>
<p>I have created a package for generating a fingerprint in React. As far as I've tested the fingerprints are unique, have the same result for incognito mode and so on. Usage is <em>very</em> simple and if you are interested, <a href="https://docs.exerra.xyz/docs/npm-packages/react-fingerprint/v1.x.x/intro" target="_blank" rel="nofollow">check out the docs</a>.</p>
<p>It is made in Typescript so if you are using Typescript (you should if you aren't) then types are already built in. No reason to install a seperate <code>@types</code> package 😊</p>
<hr>
<h1 id="website-overhaul">Website overhaul</h1>
<p>If you have visited my website in the past few months, you would have noticed that my website is different from the one before. Lemme tell you a few of the things changed</p>
<h2 id="framework">Framework</h2>
<p>I have recently fallen in love 💜 with Remix. I quite like the versatility of it and the SSR aspect. Because of that, I have made the website in Remix and TailwindCSS.
I much prefer Tailwind over CSS because I find CSS files to generally be a mess of "where is this colour defined".</p>
<p>Beforehand, I was using a flavour of Vue that could be added to static HTML files to make them interactive. I had a mock "api" (and I am using that term <strong>very</strong> loosly) to fetch data about me client-side and then fill that data in. That proved to be quite a good method for easily changing the content of stuff, but was quite inefficient due to having quite a bunch of requests. Now the data sits in the loader function of the page which means that no API calls have to be done for them, not even server-side.</p>
<h2 id="design">Design</h2>
<p>I could've went with the old design, but I thought that something more fun would be better, so I overhauled the design to be bright, colourful and card-shaped.</p>
<p>Each little piece of info is in its own card which makes it pop out more against the background.</p>
<h2 id="lastfm">Last.fm</h2>
<p>I have also added a Last.fm widget in the "About me" section. I spent quite a while figuring out how to do it. On one hand, if I only render it server-side it will not update when I start playing a different song, but if I only render it client-side then the user may not see it due to having JS be blocked (and Remix is about working without JS).</p>
<p>So I went with a hybrid approach. The server fetches the info and renders it onto the page, then sends it out to the client. When the client has received it, provided they have JS on, React starts a 10 second interval where it checks for changes and updates the widget accordingly.</p>
<p>Positives are that it works both with and without JS (and even updates!), but the negatives are that due to the server making an API call the time to first byte (<a href="https://web.dev/ttfb/" target="_blank" rel="nofollow">TTFB</a>) is quite larger. Ultimately, I have decided that the <a href="https://web.dev/ttfb/" target="_blank" rel="nofollow">TTFB</a> isnt large enough to go full client-side rendering for the widget.</p>
<p><strong>18 Nov 2022 update</strong>: The widget is now entirely client-side for better SEO.</p>
<hr>
<h1 id="docs">Docs</h1>
<p>As you might've seen, I have a docs website. It's purpose is to serve as a unified documentation system on all of my projects. It is built with Docusaurus which means that it is pretty, fast and customisable. I have already put my API docs, package docs and bot docs there and if I ever create a new type of project that needs docs, it will be there.</p>
<hr>
<h1 id="why-are-some-cdn-links-broken">Why are some CDN links broken?</h1>
<p><img src="https://cdn.exerra.xyz/gif/404.gif" alt="Gif of a 404 page"></p>
<p>Back when it was created, my CDN was meant as a quick way of storing files useful for my projects. It still is like that, though I have added stuff for the general public (Beta profiles of Apple OS's and so on). Eventually it grew into what I consider to be critical infrastructure</p>
<p>... and as with all critical infrastructure, eventually <em>something</em> goes wrong.</p>
<h2 id="what-happened">What happened</h2>
<p>I was clearing out some temporary files and accidentally forgot to add the file to the <code>rm</code> command. What should've been <code>rm -Rf cdn/file.zip</code> turned out to be <code>rm -Rf cdn/</code>. Thankfully, I had a backup, but it was quite old and so a bunch of additions were missing.</p>
<h2 id="cdn-imperfections">CDN imperfections</h2>
<p>As you can see, a quick command can wipe out an <em>entire</em> CDN used by a lot of projects. So, what exactly is the issue and are there any other future problems?</p>
<h3 id="hosting">Hosting</h3>
<p>The CDN is hosted on a single server along with the API. As it is not the best server, if it gets overloaded for any reason (DDoS (which shouldn't happen due to Cloudflare but whatever), API overloaded, etc) then the CDN gets shut down as well. I have an idea on how to solve this, so keep on reading.</p>
<h3 id="backups">Backups</h3>
<p>A single command wiped out my entire CDN, but what saved me? <strong>A backup!</strong></p>
<p>Unfortunately, it was quite old and thus a bunch of stuff was missing.</p>
<p>Backups for the CDN are done on Google Drive using rclone. Unfortunately, I am not in the habit of subscribing to every thing I see so I am limited to 15gb which is not ideal for a CDN of around 30gb.</p>
<h2 id="what-can-be-done">What can be done?</h2>
<p>I am looking into moving the CDN to AWS (S3 + CloudFront) in order to have reliability, idiot-proofing (can't accidentally <code>rm -Rf</code> the CDN) and easier backups.</p>
<p>Pricing is quite good as well for (what I think) is usual usage.</p>
<blockquote>
<!--StartFragment-->
<p>Tiered price for: 50 GB</p>
<p>50 GB x 0.0240000000 USD = 1.20 USD</p>
<p>Total tier cost = 1.2000 USD (S3 Standard storage cost)</p>
<p>50,000 GET requests in a month x 0.00000042 USD per request = 0.021 USD (S3 Standard GET requests cost)</p>
<p>200 GB x 0.0008 USD = 0.16 USD (S3 select returned cost)</p>
<p>1.20 USD + 0.021 USD + 0.16 USD = 1.38 USD (Total S3 Standard Storage, data requests, S3 select cost)</p>
<p><strong>S3 Standard cost (monthly): 1.38 USD</strong></p>
<!--EndFragment-->
</blockquote>
<p>I will experiment with that in the coming months and if it works good, I will switch the CDN to it while preserving the same URL structure (if even possible 😵‍💫)</p>]]></description><pubDate>Thu, 18 Aug 2022 00:00:00 GMT</pubDate><media_content width="560" url="https://blog.exerra.xyz/assets/blog/casual-life-3d-likes.webp" /></item><item><title><![CDATA[The silence, it's deafening]]></title><link>https://blog.exerra.xyz/blog/silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence</link><guid>https://blog.exerra.xyz/blog/silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence-silence</guid><description><![CDATA[<h1 id="where-i-went">Where I went</h1>
<p>There's a little app I'm making. Now, I won't spoil it because it is not nearly done (still working on iCloud stuff), but what I can say is that it is sweet, will be 1,99$ and will only be on iOS (possibly on M1 too if it works well there). My goal is to make it be iOS 14 and up, but supporting 14 is quite difficult and if the market share is big enough I may go with iOS 15 and up (it will upset my jailbroken friends, but just don't jailbreak :fr:).</p>
<hr>
<h1 id="node-musickit-api">node-musickit-api</h1>
<p>A month(?) ago I released node-musickit-api. It is a simple to use and elegant wrapper for the Apple Music API. Currently it supports both catalogue and personalized routes. The package also handles authentification, but for personalized routes you will need to somehow obtain a user token because Apple doesn't have any methods for that in the documentation. If that seems like something you're interested in you can check out <a href="https://musickit.js.org/#/" target="_blank" rel="nofollow">the documentation</a>.</p>
<hr>
<h1 id="karen-bot">Karen Bot</h1>
<p>This section focuses on various things that have happened to Karen Bot</p>
<h2 id="twitter">Twitter</h2>
<p>Karen has a twitter now! It just informs about status updates for now, but it may do even more in the future :eyes:</p>
<p><twitterfollowbutton screenname="{&#x27;KarenBotDiscord&#x27;}/"></twitterfollowbutton></p>
<h2 id="code-updates">Code updates</h2>
<p>Karen Bot has not been receiving that many code updates. I've only committed these things to the repo:</p>
<ol>
<li>Changed some outdated images that returned a 404 (now hosted on my CDN)</li>
<li>Changed some embed colours</li>
<li>Added a self-hosted GitHub runner that along with a GitHub action automatically deploys code</li>
</ol>
<p>The GitHub runner was a bit interesting to set up, which I had done because I wanted to change hosting from Heroku to my own VPS. First I needed to create a relatively safe environment for Karen Bot to run in; I accomplished it by creating a new user with barely any permissions that can't do anything crazy. Then, I had to create a script to launch Karen Bot which was super easy to do with GitHub runners and only took me about 20 mins to create and iron out the imperfections. With this new switch of hosting providers I can ensure that Karen Bot has minimal downtime which most likely only will be due to deploying code. Sadly, though, during the whole process Karen Bot was down for which I apologize, From now on Karen Bot will have minimal downtime.</p>
<hr>
<h1 id="api">API</h1>
<p>For those who don't know, my API handles most of Karen Bot's tasks alongside with some other comparatively insignificant things.</p>
<h2 id="switch-from-ipv4-to-ipv6">Switch from IPv4 to IPv6</h2>
<p>During 2021-11-01 I decided to switch the API to IPv6. In theory it should make speeds faster</p>
<p>.. in theory</p>
<p>You see, IPv6 transmits larger packets, which for small requests isn't ideal. Along that, many ISPs have terrible support for IPv6 resulting in some peoples speeds dropping to a halt. Previously the response time for a request made in the US from my monitoring software was roughly 200, maybe 250 ms; after adding IPv6 it doubled to around 500ms</p>
<p><img src="https://cdn.exerra.xyz/files/png/api_response_time_nov1-nov2.png" alt="Chart showing historical response times" title="Response times"></p>
<p>Because of all of this, I will schedule API maintenance on 2021-11-09 to go back to IPv4. During that the API will be down.</p>
<h2 id="possible-future-of-the-api">Possible future of the API</h2>
<h3 id="websockets">Websockets</h3>
<p>Currently the API only supports HTTPS endpoints, but I am exploring the idea of having certain things be websockets. I may rewrite Karen Bot and it's segment of the API to use websockets allowing the server to send events as soon as they happen (e.g guild settings changed and stuff). It's not a guarantee, and profiles definitely won't be rewritten, but for some aspects websockets make way more sense than webhooks.</p>
<h3 id="streaming-platform">Streaming platform</h3>
<p>This one will be purely for fun, but I am thinking of creating something similar to Twitch to stream/record gameplay. It is not intended for a lot of people to use, mostly just me doing it for fun, but if I do manage to make it I will definitely write a guide :)</p>
<h2 id="mail-server">Mail server</h2>
<p>I am thinking of making a mail server (mostly for myself) because I don't want to pay Zoho (my current mail provider). Unfortunately, I haven't had that much luck seeing any good mail servers with relatively simple install steps, so if you have any please send it to exerra@exerra.xyz :)</p>
<p>I can also offer a service to have @exerra.xyz emails if people really want it</p>
<hr>
<h1 id="a-few-ending-statements">A few ending statements</h1>
<p>Congratulations, you made it to the end. I hope you liked my blog post, I spent all of my free time writing it haha</p>
<p>If you want to support me or the work I do, you can always donate either on <a href="https://ko-fi.com/exerra" target="_blank" rel="nofollow">Kofi</a>, <a href="https://liberapay.com/Exerra/" target="_blank" rel="nofollow">LiberaPay</a> or <a href="https://paypal.me/exerrabusiness" target="_blank" rel="nofollow">directly thru Paypal</a></p>
<p>If you're thinking of using DigitalOcean, you can use my promo code to get credits worth 100 USD for 60 days and when you spend 25 USD I will get 25 USD in credits! It is a mutually benefitial deal</p>
<p><a href="https://www.digitalocean.com/?refcode=724deb483716&#x26;utm_campaign=Referral_Invite&#x26;utm_medium=Referral_Program&#x26;utm_source=badge" target="_blank" rel="nofollow"><img src="https://web-platforms.sfo2.digitaloceanspaces.com/WWW/Badge%203.svg" alt="DigitalOcean Referral Badge"></a></p>]]></description><pubDate>Sun, 07 Nov 2021 00:00:00 GMT</pubDate><media_content width="560" url="https://cdn.exerra.xyz/png/icons8/casual-life/clock-and-calendar_512x356.png" /></item><item><title><![CDATA[Newsletter time]]></title><link>https://blog.exerra.xyz/blog/newsletter-time</link><guid>https://blog.exerra.xyz/blog/newsletter-time</guid><description><![CDATA[<p>Hiya!<br>
This will be a super small update, but an update nevertheless.<br>
SO, I've been working on a newsletter! Basically it sends an automatic e-mail when a new blog entry is created or updated. Currently I am working on UI for subscribing and UI for unsubscribing.</p>
<h1 id="subscribe-ui">Subscribe UI</h1>
<p>So, I will plaster it accross my blog and probably use cookies to hide it if a person is already subscribed. I imagine it to be a small modal, not a big popup but something like a snackbar.</p>
<p><img src="https://lh3.googleusercontent.com/mkAGtJWvzMejuVdgYx_9uOEoiMEuo2jYI9YLXf04TgTo7ywtIf2LVAkHHzyMYUmmvPCzEJsUGZHwj3gUchMq5WFGCNUH2j28gOgG=w1064-v0" alt="Image of a Material UI popup" title="Source: Material.io"></p>
<p><em>Source: Material.io</em></p>
<p>It will have a brief intro about what the newsletter does, a text field to input the email, a subscribe button and of course a close button.</p>
<h1 id="unsubscribe-ui">Unsubscribe UI</h1>
<p>So, currently on subscribtion emails you get sent a link to unsubscribe, which is pretty easy to do since the email is sent only to <strong>you</strong>. BUT the newsletter updates that get sent, get sent using BCC which means that there is no personalization. To combat this, I will make a probably simple and not elegant UI which lets you input the email address and unsubscribe.</p>
<h1 id="muh-spam">Muh spam</h1>
<p>Yeah... no spam here. I don't write constant blog updates and you only get emails when a new blog entry is created or updated :)</p>
<h1 id="fin">Fin</h1>
<p>So this is the end. I hope you liked the idea, if not then give me feedback wherever you can. Also please do subscribe (when the option is available) if you like my blog. Reaching out to people is hard, especially if a lot of people accidentally stumble unto this because of a google search or something :)</p>]]></description><pubDate>Wed, 02 Jun 2021 00:00:00 GMT</pubDate></item><item><title><![CDATA[Data Collection for Karen Bot]]></title><link>https://blog.exerra.xyz/blog/data-collection-karen-bot</link><guid>https://blog.exerra.xyz/blog/data-collection-karen-bot</guid><description><![CDATA[<p>Hiya!<br>
Here I will discuss my <strong>plans</strong> for data collection. I will be serious here since it is quite a delicate subject. <strong>This is not finalized</strong> yet and is <strong>just an idea</strong>. All criticism can be directed towards my Twitter (<a href="https://twitter.com/Exerra" target="_blank" rel="nofollow">@Exerra</a>) or my email (<a href="mailto:exerra@exerra.xyz">exerra@exerra.xyz</a>). <strong>Your feedback is important</strong> to me and I really want to create the best experience for Karen Bot.</p>
<h1 id="for-users">For users</h1>
<p>So, don't worry it won't be so severe as Google or something. It will just interact with the Spotify commands and display your most searched for artists and their genres. <strong>None</strong> of the data will be exported to 3rd parties and it will be opt-in only.</p>
<h2 id="so-how-will-it-work">So how will it work</h2>
<p>As mentioned before, it will interact with the <strong>Spotify</strong> commands to get your searched <strong>artists</strong> and their <strong>genres</strong> (e.g if you search for "Boyshit" it will send that you searched for a song by Madison Beer, and then send Madison Beer's genres) and after a while it could develop an understanding for what artists and genres you like and display them in your profile.</p>
<h2 id="what-about-my-privacy">What about my privacy</h2>
<p>Data is <strong>not</strong> shared with 3rd parties. It stays safe in my servers. It will be <strong>opt-in only</strong> and <strong>you will be able to delete that data</strong> if need arises. I am actually wondering if I should implement a super easy way or data erasure only by contacting me thru email, send me feedback wherever you can about that, please.</p>
<h2 id="what-will-be-the-benefit">What will be the benefit</h2>
<p>A nicer and more feature-rich profile command that can display data based on how you use Karen Bot.</p>
<h1 id="more-advanced-stuff">More advanced stuff</h1>
<h2 id="how-will-it-work">How will it work</h2>
<p>All profile related things stay on my servers. When you use profile commands it takes data from my central server. What I want to do here is that when Karen Bot's Spotify features get triggered, it fetches the Spotify API, then gets the artist and then fetches data for the artist, and then gets their genres (because Spotify API doesn't display genres for albums or songs) and sends that data over to my server which then processes it and saves it in the database. It won't immediately display that stuff in the profile, instead it will wait for enough data to accumulate so it can understand what the user likes and <em>then</em> display it.</p>
<h2 id="opt-in-system">Opt-in system</h2>
<p>I actually haven't decided how the opt-in will work. I am wondering if I should make the Spotify and Profile features just not work unless the user has opted in, or, limit their functionality. Again, please send me feedback everywhere you can.</p>]]></description><pubDate>Sun, 23 May 2021 00:00:00 GMT</pubDate></item></channel></rss>