<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://mifi.no/blog/</id>
    <title>mifi.no Blog</title>
    <updated>2026-03-13T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://mifi.no/blog/"/>
    <subtitle>mifi.no Blog</subtitle>
    <icon>https://mifi.no/favicon.ico</icon>
    <entry>
        <title type="html"><![CDATA[2026 Mount Aso, Japan helicopter accident]]></title>
        <id>https://mifi.no/blog/2026/03/13/mount-aso/</id>
        <link href="https://mifi.no/blog/2026/03/13/mount-aso/"/>
        <updated>2026-03-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Probably my scariest travel experience ever happened right here, on the volcano of Mount Aso in southwestern Japan, and it's not what you think. Please help me solve this riddle of an experience which I cannot explain.]]></summary>
        <content type="html"><![CDATA[<p>Probably my scariest travel experience ever happened right here, on the volcano of Mount Aso in southwestern Japan, and it's not what you think. Please help me solve this riddle of an experience which I cannot explain.</p>
<p>At around 12pm on feb 22, we arrived at the parking lot of this hugely popular attraction, and to our surprise the parking lots are quite empty. But there are huge crowds walking around aimlessly in and around the tourist information center. After a while we notice that the last bit of the road leading up to the volcano rim is closed off, and at the information center there's a sign saying "Mount Aso closed due to sightseeing incident". Suddenly I remembered that on Google Maps when I checked the day before it said that the attraction was temporarily closed. Now I'm curious, so I "Google Mount Aso incident", and sure enough, up comes several news articles dated february 20, 2026 stating that a helicopter and its 3 passengers had been "recovered". Oh no, so they're still working on recovering the helicopter and that's why the crater is still closed, I thought. Then I scroll further and I find a Wikipedia article named "2026 Mount Aso helicopter crash". It says:</p>
<p>"On January 20, 2026, a Robinson R44 Raven II helicopter on a sightseeing tour crashed inside Mount Aso.".</p>
<p>Now I'm thinking, what? There were two helicopter incidents at the same mountain exactly 1 month apart? This cannot be possible. There must have been a mistake somewhere. I just conclude that february 20 must be the correct date, and that's why the crater is still closed, because it takes a few days to recover and cleanup the wreckage site, it makes sense.</p>
<p>I start looking around the parking, and it turns out that there is also a hiking trail to go up to the crater, but I see only one other person walking there. I start walking up the trail but I arrived at a gate with a Japanese official guarding it. She shows me a map, and to my surprise we're still allowed to continue hiking, as long as we don't go up to the crater rim (to he left). Cool! I'll just go for a short walk and look at the other trails/peaks in the area.</p>
<p>Meanwhile my curiosity starts to itch, and I start Googling again. Where exactly did this helicopter crash? I found an official video from the Japanese authorities showing the exact coordinates. I plot them into Google Maps and it shows the location right there, inside the crater rim. Yikes! It's not even a remote area, it's right here, in plain sight, 400m opposite the crater rim viewing platform (which is accessible by car but now closed off from tourists). I continue reading the Wikipedia article and I now see the whole timeline of events.</p>
<p>"On January 20 at 10:52 a.m. JST, the helicopter took off and was en route for the local sightseeing flights of Mount Aso. The 3 occupants included 2 Taiwanese tourists and the pilot. All three onboard, including two Taiwanese tourists and the pilot, remain missing (to this day)."</p>
<p>"On January 20, the Kumamoto police force succeeded in finding the crashed helicopter in the afternoon. However, the rescue was halted due to ground freezing, volcanic gases and strong winds on Mount Aso. As of February 11, rescue operations could not begin after the accident."</p>
<p>"On February 18, almost a month after the incident, the fire department and the police force announced that they had spotted what appeared to be three people on board a crashed sightseeing helicopter via drone."</p>
<p>That's when I realised, the helicopter wreckage is sitting there, with 3 dead people inside, and it's been sitting there for <em>almost a month</em> before the Japanese authorities decided to fly a drone to look for survivors. I'm now writing this on mar 12, the helicopter is still there and the crater is still closed.</p>
<p>When I asked about this in a Japanese subreddit, someone said that "there were news when the accident happened that the area has laws against flying drones because they won't be able to retrieve the drone if it went down - they probably have to wait to get permission before flying a drone". In other words: The authorities deem the area so dangerous that they don't allow flying a drone there in fear of not being able to recover a lost drone, yet they allow helicopters flying in the same area, every day <strong>with tourists</strong>. It doesn't make any sense. In an emergency we must have prioritizations. If they flew a drone as soon as they heard of the accident, they could have even had the chance to save lives! But after 1 month the chance of survivals is near 0. Is Japan really so crippled by bureaucracy that it takes a month to approve an emergency drone flight to potentially save human lives?</p>
<p>I feel very sad for the victims' families, and I'm shocked that this can happen in such a developed country like Japan. I think if an accident like this happened in any other country in the world, people would throw everything they have and do what it takes in hopes of saving lives. Yet here we are, almost 2 months later. No more news from Japanese authorities. What am I missing here?</p>
<p>Sources:</p>
<ul>
<li><a href="https://www.reddit.com/r/japannews/comments/1r7za4k/aso_sightseeing_helicopter_accident_three_people/" target="_blank" rel="noopener noreferrer">https://www.reddit.com/r/japannews/comments/1r7za4k/aso_sightseeing_helicopter_accident_three_people/</a></li>
<li><a href="https://www.reddit.com/r/japannews/comments/1qhtj09/helicopter_with_2_passengers_from_taiwan_goes/" target="_blank" rel="noopener noreferrer">https://www.reddit.com/r/japannews/comments/1qhtj09/helicopter_with_2_passengers_from_taiwan_goes/</a></li>
<li><a href="https://en.wikipedia.org/wiki/2026_Mount_Aso_helicopter_crash" target="_blank" rel="noopener noreferrer">https://en.wikipedia.org/wiki/2026_Mount_Aso_helicopter_crash</a></li>
<li><a href="https://earth.google.com/web/search/32%c2%b053%2708.0%22N+131%c2%b005%2715.0%22E/@32.88613981,131.08494281,1232.37526816a,1030.57823439d,35y,-13.84207642h,60.73807392t,0.00000119r/data=CmQaNhIwGRNlyuJZcUBAIc3MzMzMYmBAKhwzMsKwNTMnMDguMCJOIDEzMcKwMDUnMTUuMCJFGAEgASImCiQJnqb72s79NkARmqb72s79NsAZuLU2zP89SkAhWzAbPHiISsBCAggBOgMKATBCAggASg0I____________ARAA" target="_blank" rel="noopener noreferrer">https://earth.google.com/web/search/32%c2%b053%2708.0%22N+131%c2%b005%2715.0%22E/@32.88613981,131.08494281,1232.37526816a,1030.57823439d,35y,-13.84207642h,60.73807392t,0.00000119r/data=CmQaNhIwGRNlyuJZcUBAIc3MzMzMYmBAKhwzMsKwNTMnMDguMCJOIDEzMcKwMDUnMTUuMCJFGAEgASImCiQJnqb72s79NkARmqb72s79NsAZuLU2zP89SkAhWzAbPHiISsBCAggBOgMKATBCAggASg0I____________ARAA</a></li>
</ul>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="travel" term="travel"/>
        <category label="japan" term="japan"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[TinyShare — TypeScript-first social share helper]]></title>
        <id>https://mifi.no/blog/2026/02/16/tinyshare/</id>
        <link href="https://mifi.no/blog/2026/02/16/tinyshare/"/>
        <updated>2026-02-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[TinyShare is a tiny, TypeScript-first helper that builds social sharing URLs and opens share windows. It’s a single-file, dependency-free, programmatic alternative to DOM-bound sharer libraries.]]></summary>
        <content type="html"><![CDATA[<p>TinyShare is a tiny, TypeScript-first helper that builds social sharing URLs and opens share windows. It’s a single-file, dependency-free, programmatic alternative to DOM-bound sharer libraries.</p>
<p>Why use it?</p>
<ul>
<li>Type-safe options per service (good autocompletion and compile-time checks).</li>
<li>Minimal: no runtime dependencies, small footprint.</li>
<li>Programmatic: call <code>share(...)</code> from your UI code — no attribute bindings.</li>
</ul>
<p>Example</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token keyword" style="font-style:italic">import</span><span class="token plain"> share </span><span class="token keyword" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'tinyshare'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token function" style="color:rgb(130, 170, 255)">share</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'https://example.com'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'twitter'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> title</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'Hello'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> hashtags</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'news'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><br></span></code></pre></div></div>
<p>See the repo: <a href="https://github.com/mifi/tinyshare" target="_blank" rel="noopener noreferrer">https://github.com/mifi/tinyshare</a> — MIT licensed.</p>
<p>Credits: Forked from <a href="https://github.com/ellisonleao/sharer.js" target="_blank" rel="noopener noreferrer">Sharer.js</a> by Ellison Leao.</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="typescript" term="typescript"/>
        <category label="javascript" term="javascript"/>
        <category label="open-source" term="open-source"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Valutakalkulator med historiske valutakurser fra Norges Bank]]></title>
        <id>https://mifi.no/blog/valutakalkulator-med-historiske-valutakurser-fra-norges-bank/</id>
        <link href="https://mifi.no/blog/valutakalkulator-med-historiske-valutakurser-fra-norges-bank/"/>
        <updated>2024-11-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Jeg vet ikke hvorfor det ikke fantes før, så jeg lagde en slik kalkulator.]]></summary>
        <content type="html"><![CDATA[<p>Jeg vet ikke hvorfor det ikke fantes før, så jeg lagde en slik kalkulator.</p>
<p>Konverter valuta på historisk dato basert på data fra Norges Banks API. Sjekk ut <a href="https://mifi.no/tools/currency-calc/">valutakalkulatoren</a>.</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Knowledge base]]></title>
        <id>https://mifi.no/blog/knowledge-base/</id>
        <link href="https://mifi.no/blog/knowledge-base/"/>
        <updated>2024-10-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I've got my open source knowledge base!]]></summary>
        <content type="html"><![CDATA[<p>I've got my open source knowledge base!</p>
<p>I created a personal knowledge base. Inspired by <a href="https://github.com/nikitavoloboev" target="_blank" rel="noopener noreferrer">@nikitavoloboev</a>'s <a href="https://wiki.nikiv.dev/" target="_blank" rel="noopener noreferrer">"Everything I Know"</a>. <a href="https://mifi.no/docs/">Check it out!</a></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Automatically import YouTube playlist from Spotify]]></title>
        <id>https://mifi.no/blog/automatically-import-youtube-playlist-from-spotify/</id>
        <link href="https://mifi.no/blog/automatically-import-youtube-playlist-from-spotify/"/>
        <updated>2024-08-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I created a tool to automatically migrate my playlists from Spotify to YouTube Music]]></summary>
        <content type="html"><![CDATA[<p>I created a tool to automatically migrate my playlists from Spotify to YouTube Music</p>
<p>For more info, see <a href="https://github.com/mifi/youtube-import-playlist" target="_blank" rel="noopener noreferrer">youtube-import-playlist</a>.</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Installing TeamViewer without root/admin on MacOS]]></title>
        <id>https://mifi.no/blog/installing-teamviewer-without-rootadmin-on-macos/</id>
        <link href="https://mifi.no/blog/installing-teamviewer-without-rootadmin-on-macos/"/>
        <updated>2023-11-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to install TeamViewer .pkg on a Mac without admin privileges ☺️]]></summary>
        <content type="html"><![CDATA[<p>How to install TeamViewer <code>.pkg</code> on a Mac without admin privileges ☺️</p>
<p>See also <a href="https://mifi.no/docs/macos/macos-installing-pkg/">this article</a>.</p>
<p>It used to be so easy to connect to someone's computer using TeamViewer, because TeamViewer used to provide a Chrome app, however after 2022 they don't provide that anymore, and when downloading TeamViewer, we are presented with an <code>Install TeamViewer.app</code> that requires admin privileges to install.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="there-is-a-solution">There is a solution<a href="https://mifi.no/blog/installing-teamviewer-without-rootadmin-on-macos/#there-is-a-solution" class="hash-link" aria-label="Direct link to There is a solution" title="Direct link to There is a solution">​</a></h2>
<p>First, copy <code>Install TeamViewer.app</code> from inside the <code>.dmg</code> to your home folder. Now we can extract the <code>.pkg</code> file within:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">pkgutil --expand-full Install\ TeamViewer.app/Contents/Resources/Install\ TeamViewer.pkg TeamViewer-extracted/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">ls TeamViewer-extracted</span><br></span></code></pre></div></div>
<p>Notice how the <code>TeamViewer-extracted</code> folder contains a lot of crap: 💩</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">AuthPluginPackage.pkg/ RemotePrintingPackage.pkg/ Distribution Resources/ FullEnforceUIVersionPackage.pkg/ TeamViewerApp.pkg/ FullSilentInstallerPackage.pkg/ UninstallerAppPackage.pkg/ PriviledgedHelperPackage.pkg/ UninstallerHelperPackage.pkg/ RemoteAudioDriverPackage.pkg/</span><br></span></code></pre></div></div>
<p>We don't need any of those. Instead we will just grab the <code>TeamViewer.app</code> from inside <code>TeamViewerApp.pkg</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">mv TeamViewer-extracted/TeamViewerApp.pkg/Payload/TeamViewer.app ~/Desktop/</span><br></span></code></pre></div></div>
<p>Then we can simply run our <em>admin privilege-free</em> <code>TeamViewer.app</code> without installing any of the other invasive stuff and we can flush the rest of it down the 🚽</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">rm -rf TeamViewer-extracted </span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why you should use Cache-Control: no-store on index.html]]></title>
        <id>https://mifi.no/blog/why-you-should-use-cache-control-no-store-on-indexhtml/</id>
        <link href="https://mifi.no/blog/why-you-should-use-cache-control-no-store-on-indexhtml/"/>
        <updated>2023-10-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Because of iOS Safari.]]></summary>
        <content type="html"><![CDATA[<p>Because of iOS Safari.</p>
<p>I would say Safari on iOS has become the new IE - they just <em>have</em> to do things a little bit different.</p>
<p>Isn't it nice to have a HTTP standard <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" target="_blank" rel="noopener noreferrer">Cache-Control</a> response header that lets us control how browsers cache our content? Well yes, until of course one of the most popular browsers doesn't follow that standard.</p>
<p>When building a website, e.g. single page application, we like to set <code>Cache-Control</code> <code>no-cache</code> or <code>max-age=300, must-revalidate</code> on the index page (<code>/</code>). That way, the browser will always revalidate with the server before considering using a cached version, and we can make sure that we're not serving old content to the user when pushing updates! 🙌 This seems to work nicely in most browsers. Even Safari on MacOS behaves like this. But not Safari on iOS 🤡</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-reproduce-">How to reproduce 🤔<a href="https://mifi.no/blog/why-you-should-use-cache-control-no-store-on-indexhtml/#how-to-reproduce-" class="hash-link" aria-label="Direct link to How to reproduce 🤔" title="Direct link to How to reproduce 🤔">​</a></h2>
<p>First start a HTTP server, e.g.:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports">http</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'http'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">http</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">createServer</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:rgb(137, 221, 255)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token console class-name" style="color:rgb(255, 203, 107)">console</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">log</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">req</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">method</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">url</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> req</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">headers</span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token string" style="color:rgb(195, 232, 141)">'user-agent'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">setHeader</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'Cache-Control'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'no-cache'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">write</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'Hello World!'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">end</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">listen</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token number" style="color:rgb(247, 140, 108)">8080</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">node index.mjs</span><br></span></code></pre></div></div>
<p>Now open Safari on iOS and open your computer's IP in Safari <code>http://192.168.1.x:8080</code></p>
<p>Observe an expected log line from the server:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">GET / Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1</span><br></span></code></pre></div></div>
<p>Now kill the Safari app and then repoen it. <strong>This will cause Safari to load the page from its local cache without revalidating.</strong></p>
<p>Even adding <code>no-cache, must-revalidate, max-age=0</code> doesn't help. Safari still doesn't revalidate.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="solution-">Solution 🚀<a href="https://mifi.no/blog/why-you-should-use-cache-control-no-store-on-indexhtml/#solution-" class="hash-link" aria-label="Direct link to Solution 🚀" title="Direct link to Solution 🚀">​</a></h2>
<p>The only sensible thing that makes sure nobody dares cache <code>index.html</code> is to set <code>Cache-Control: no-store</code>. This is not optimal, because it doesn't let us cache the page even for a short duration, and the browser always has to fully fetch the whole file every time, but hey, at least it works!</p>
<p>Alternatively, some people use <code>Vary: *</code>, which also seems to work, however I'm not sure how safe that is.</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[VPN for censored state like China (GFW) - 2023]]></title>
        <id>https://mifi.no/blog/vpn-for-censored-states-like-china-gfw/</id>
        <link href="https://mifi.no/blog/vpn-for-censored-states-like-china-gfw/"/>
        <updated>2023-09-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Note: Moved here.]]></summary>
        <content type="html"><![CDATA[<p><strong>Note: <a href="https://mifi.no/docs/travel/censorship/">Moved here</a>.</strong></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="vpn" term="vpn"/>
        <category label="censorship" term="censorship"/>
        <category label="china" term="china"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Raspberry Pi AirPlay Mirroring server]]></title>
        <id>https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/</id>
        <link href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/"/>
        <updated>2023-08-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How to setup an AirPlay server on a Raspberry Pi.]]></summary>
        <content type="html"><![CDATA[<p>How to setup an AirPlay server on a Raspberry Pi.</p>
<p>Start with a fresh Raspberry Pi OS Bullseye <strong>Lite</strong>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setup-wifi">Setup WiFi<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#setup-wifi" class="hash-link" aria-label="Direct link to Setup WiFi" title="Direct link to Setup WiFi">​</a></h2>
<p><a href="https://wiki.archlinux.org/title/wpa_supplicant" target="_blank" rel="noopener noreferrer">https://wiki.archlinux.org/title/wpa_supplicant</a></p>
<p>First run <code>raspi-config</code> and enable WiFi.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">wpa_cli -i wlan0</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">scan</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">scan_results</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">add_network</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">set_network 0 ssid "MYSSID"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">set_network 0 psk "passphrase"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">enable_network 0</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">save_config</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">quit</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="install-x11">Install X11<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#install-x11" class="hash-link" aria-label="Direct link to Install X11" title="Direct link to Install X11">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt-get install --no-install-recommends xserver-xorg xserver-xorg-legacy xinit x11-xserver-utils xinput libnss3</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo usermod -a -G tty pi</span><br></span></code></pre></div></div>
<p><code>sudo nano /etc/X11/Xwrapper.config</code> and change it to:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">allowed_users = anybody</span><br></span></code></pre></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="openbox">Openbox<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#openbox" class="hash-link" aria-label="Direct link to Openbox" title="Direct link to Openbox">​</a></h3>
<p>Openbox is needed for fullscreen / scaling to work properly. Without a Window manager, the window will probably get cropped.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt install openbox</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="install-uxplay">Install UxPlay<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#install-uxplay" class="hash-link" aria-label="Direct link to Install UxPlay" title="Direct link to Install UxPlay">​</a></h2>
<p>Reference: <a href="https://github.com/FDH2/UxPlay" target="_blank" rel="noopener noreferrer">https://github.com/FDH2/UxPlay</a></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt install cmake build-essential pkg-config</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"># uxplay deps</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt-get install libx11-dev libssl-dev libplist-dev libavahi-compat-libdnssd-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base gstreamer1.0-libav gstreamer1.0-plugins-good gstreamer1.0-plugins-bad</span><br></span></code></pre></div></div>
<p>Build UxPlay</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">git clone https://github.com/FDH2/UxPlay</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">cd UxPlay</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">cmake .</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">make</span><br></span></code></pre></div></div>
<p>To test it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain"># one terminal:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">startx</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"># other terminal:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">DISPLAY=:0 ./uxplay -bt709 -nh -n TV -fs</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setup-audio">Setup audio<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#setup-audio" class="hash-link" aria-label="Direct link to Setup audio" title="Direct link to Setup audio">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt install gstreamer1.0-alsa</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo usermod -a -G audio pi</span><br></span></code></pre></div></div>
<p>Edit <code>/home/pi/.asoundrc</code></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">pcm.!default {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  type asym</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  playback.pcm {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    type plug</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    slave.pcm "output"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  capture.pcm {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    type plug</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    slave.pcm "input"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">pcm.output "hdmi:CARD=vc4hdmi,DEV=0"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">ctl.!default {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  type hw</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  card 1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<p><code>sudo raspi-config</code> then choose: System -&gt; Audio -&gt; HDMI</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setup-simple-desktop-run-on-boot">Setup simple desktop (run on boot)<a href="https://mifi.no/blog/raspberry-pi-airplay-mirroring-server/#setup-simple-desktop-run-on-boot" class="hash-link" aria-label="Direct link to Setup simple desktop (run on boot)" title="Direct link to Setup simple desktop (run on boot)">​</a></h2>
<p>We'll use LightDM to provide a desktop.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">sudo apt install -y lightdm</span><br></span></code></pre></div></div>
<p><code>sudo nano /etc/lightdm/lightdm.conf</code> and change these under <code>[Seat:*]</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">autologin-user=pi</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">xserver-command=X -nocursor</span><br></span></code></pre></div></div>
<p>Then <code>nano ~/.xsession</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">xset s off</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">xset -dpms</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">xset s noblank</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">openbox &amp;</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">while true; do</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  /home/pi/UxPlay/uxplay -bt709 -nh -n TV -fs -as alsasink -nohold</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  sleep 5</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">done</span><br></span></code></pre></div></div>
<p><code>sudo raspi-config</code> then enable: System -&gt; Boot -&gt; Desktop GUI, automatically logged in as 'pi' user.</p>
<p>Reboot and test!</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="airplay" term="airplay"/>
        <category label="ios" term="ios"/>
        <category label="macos" term="macos"/>
        <category label="raspberrypi" term="raspberrypi"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[DIY app cloud logging using AWS CloudWatch]]></title>
        <id>https://mifi.no/blog/diy-app-cloud-logging-using-aws-cloudwatch/</id>
        <link href="https://mifi.no/blog/diy-app-cloud-logging-using-aws-cloudwatch/"/>
        <updated>2023-07-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Need logging in your Electron app to the cloud but don't want to pay for services like Sentry, Papertrail, Bugsnag etc.?]]></summary>
        <content type="html"><![CDATA[<p>Need logging in your Electron app to the cloud but don't want to pay for services like Sentry, Papertrail, Bugsnag etc.?</p>
<p>AWS CloudWatch and <a href="https://github.com/winstonjs/winston" target="_blank" rel="noopener noreferrer">Winston</a> to the rescue!</p>
<ol>
<li>In your Electron <code>main</code> app, install and setup <a href="https://github.com/mifi/cloudwatch-winston" target="_blank" rel="noopener noreferrer">cloudwatch-winston</a>.</li>
<li>Create a log group in CloudWatch Logs</li>
<li>In IAM, create a new user with <strong>only</strong> the following policy: (replace <code>123456789012</code> with your AWS account ID and <code>my-cloudwatch-log-group</code> with your desired log group name.)</li>
</ol>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">{</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    "Version": "2012-10-17",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    "Statement": [</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            "Sid": "VisualEditor0",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            "Effect": "Allow",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            "Action": [</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "logs:CreateLogStream",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "logs:DescribeLogGroups",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "logs:DescribeLogStreams",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "logs:PutLogEvents"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            ],</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            "Resource": [</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "arn:aws:logs:*:123456789012:log-group:my-cloudwatch-log-group",</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">                "arn:aws:logs:*:123456789012:log-group:my-cloudwatch-log-group:log-stream:*"</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            ]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    ]</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">}</span><br></span></code></pre></div></div>
<ol start="4">
<li>Copy the Access Key ID and Secret Acces Key and put it in <code>cloudwatch-winston</code>, and start logging 🪵🔥</li>
</ol>
<p>Now if only Winston would work in the browser we could also do this from the web and maybe even from React Native...</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="aws" term="aws"/>
        <category label="logging" term="logging"/>
        <category label="javascript" term="javascript"/>
        <category label="electron" term="electron"/>
        <category label="nodejs" term="nodejs"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Workaround NotAllowedError in Safari]]></title>
        <id>https://mifi.no/blog/workaround-notallowederror-safari/</id>
        <link href="https://mifi.no/blog/workaround-notallowederror-safari/"/>
        <updated>2023-07-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Problem: Calling HTML5 video play() programmatically gives NotAllowedError in Safari, even with muted set on the video element. This is often a problem when trying to load some video data asynchronously before calling play(), even though the playback was initally triggered by a user's click (which is required by Safari).]]></summary>
        <content type="html"><![CDATA[<p>Problem: Calling HTML5 video <code>play()</code> programmatically gives <code>NotAllowedError</code> in Safari, even with <code>muted</code> set on the video element. This is often a problem when trying to load some video data asynchronously before calling <code>play()</code>, even though the playback was initally triggered by a user's click (which is required by Safari).</p>
<p>A workaround is to create an <em>additional</em> dummy <code>video</code> element, synchronously, when the user clicks the button, and then play it back immediately. Once that is done, the page is "whitelisted" by Safari and any other <code>&lt;video&gt;</code> tag can call <code>play</code> programmatically:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token keyword" style="font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">handleMyButtonClick</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> video </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(191, 199, 213)">document</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">createElement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'video'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  video</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">muted</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">true</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  video</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">playsInline</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">true</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic">// Generated by: ffmpeg -t 1 -f lavfi -i color=c=black:s=64x64 -c:v libx264 -tune stillimage -pix_fmt yuv420p -crf 51 -y out.mp4 &amp;&amp; base64 &lt; out.mp4</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  video</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">src</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAtptZGF0AAACrwYF//+r3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2NCByMzA5NSBiYWVlNDAwIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAyMiAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOi0zOi0zIGFuYWx5c2U9MHgzOjB4MTEzIG1lPWhleCBzdWJtZT03IHBzeT0xIHBzeV9yZD0yLjAwOjAuNzAgbWl4ZWRfcmVmPTEgbWVfcmFuZ2U9MTYgY2hyb21hX21lPTEgdHJlbGxpcz0xIDh4OGRjdD0xIGNxbT0wIGRlYWR6b25lPTIxLDExIGZhc3RfcHNraXA9MSBjaHJvbWFfcXBfb2Zmc2V0PS00IHRocmVhZHM9MiBsb29rYWhlYWRfdGhyZWFkcz0xIHNsaWNlZF90aHJlYWRzPTAgbnI9MCBkZWNpbWF0ZT0xIGludGVybGFjZWQ9MCBibHVyYXlfY29tcGF0PTAgY29uc3RyYWluZWRfaW50cmE9MCBiZnJhbWVzPTMgYl9weXJhbWlkPTIgYl9hZGFwdD0xIGJfYmlhcz0wIGRpcmVjdD0xIHdlaWdodGI9MSBvcGVuX2dvcD0wIHdlaWdodHA9MiBrZXlpbnQ9MjUwIGtleWludF9taW49MSBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9va2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9NTEuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBtYXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMjAAgAAAABtliIQAGM5//rXfMsVE8oN5hOAtnfPJtIztixMAAAMXbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAA+gAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAkF0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAAA+gAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAEAAAABAAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAAPoAAAAAAABAAAAAAG5bWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABAAAAAQABVxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAABZG1pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAASRzdGJsAAAAwHN0c2QAAAAAAAAAAQAAALBhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAEAAQABIAAAASAAAAAAAAAABFUxhdmM1OS4zNy4xMDAgbGlieDI2NAAAAAAAAAAAAAAAGP//AAAANmF2Y0MBZAAK/+EAGGdkAAqs2UQmwEQAAAMABAAAAwAIPEiWWAEAB2jr4GUTITD9+PgAAAAAEHBhc3AAAAABAAAAAQAAABRidHJ0AAAAAAAAFpAAABaQAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAAtIAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjU5LjI3LjEwMA=='</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  video</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">play</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token keyword control-flow" style="font-style:italic">catch</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token console class-name" style="color:rgb(255, 203, 107)">console</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">warn</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic">// Now continue doing async work before playing the *actual* video in a different &lt;video&gt; element</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="javascript" term="javascript"/>
        <category label="safari" term="safari"/>
        <category label="video" term="video"/>
        <category label="html5" term="html5"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Format Tesla USB stick with multiple partitions]]></title>
        <id>https://mifi.no/blog/tesla-usb-multi-partitions/</id>
        <link href="https://mifi.no/blog/tesla-usb-multi-partitions/"/>
        <updated>2023-06-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Note: Moved here.]]></summary>
        <content type="html"><![CDATA[<p><strong>Note: <a href="https://mifi.no/docs/tesla/usb-storage/">Moved here</a>.</strong></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="tesla" term="tesla"/>
        <category label="usb" term="usb"/>
        <category label="boombox" term="boombox"/>
        <category label="teslacam" term="teslacam"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using winston with electron]]></title>
        <id>https://mifi.no/blog/winston-electron-logger/</id>
        <link href="https://mifi.no/blog/winston-electron-logger/"/>
        <updated>2022-09-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Electron logs to stdout by default, so when you build a production app, those logs will not be saved anywhere. However winston opens up a whole plethora of options.]]></summary>
        <content type="html"><![CDATA[<p>Electron logs to <code>stdout</code> by default, so when you build a production app, those logs will not be saved anywhere. However <code>winston</code> opens up a whole plethora of options.</p>
<p>First install deps:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">yarn add winston electron-is-dev</span><br></span></code></pre></div></div>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic">// logger.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports">winston</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'winston'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports">util</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'util'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports">isDev</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'electron-is-dev'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token imports"> app </span><span class="token imports punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'electron'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token imports"> join </span><span class="token imports punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'path'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic">// https://github.com/winstonjs/winston/issues/1427</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(130, 170, 255)">combineMessageAndSplat</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:rgb(137, 221, 255)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token function" style="color:rgb(130, 170, 255)">transform</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token parameter">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token known-class-name class-name" style="color:rgb(255, 203, 107)">Symbol</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token keyword control-flow" style="font-style:italic">for</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'splat'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> args </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> message </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token comment" style="color:rgb(105, 112, 152);font-style:italic">// eslint-disable-next-line no-param-reassign</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">message</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> util</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">format</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">message</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token spread operator" style="color:rgb(137, 221, 255)">...</span><span class="token plain">args</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token keyword control-flow" style="font-style:italic">return</span><span class="token plain"> info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(130, 170, 255)">createLogger</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:rgb(137, 221, 255)">=&gt;</span><span class="token plain"> winston</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">createLogger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token literal-property property">format</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> winston</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">format</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">combine</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    winston</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">format</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">timestamp</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token function" style="color:rgb(130, 170, 255)">combineMessageAndSplat</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    winston</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token property-access">format</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">printf</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token parameter">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:rgb(137, 221, 255)">=&gt;</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:rgb(195, 232, 141)">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">${</span><span class="token template-string interpolation">info</span><span class="token template-string interpolation punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token template-string interpolation property-access">timestamp</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token template-string string" style="color:rgb(195, 232, 141)"> </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">${</span><span class="token template-string interpolation">info</span><span class="token template-string interpolation punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token template-string interpolation property-access">level</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token template-string string" style="color:rgb(195, 232, 141)">: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">${</span><span class="token template-string interpolation">info</span><span class="token template-string interpolation punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token template-string interpolation property-access">message</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token template-string template-punctuation string" style="color:rgb(195, 232, 141)">`</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> logDirPath </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> isDev </span><span class="token operator" style="color:rgb(137, 221, 255)">?</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'.'</span><span class="token plain"> </span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> app</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">getPath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'userData'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword" style="font-style:italic">const</span><span class="token plain"> logger </span><span class="token operator" style="color:rgb(137, 221, 255)">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">createLogger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">add</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token keyword" style="font-style:italic">new</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 107)">winston</span><span class="token class-name punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token class-name" style="color:rgb(255, 203, 107)">transports</span><span class="token class-name punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token class-name" style="color:rgb(255, 203, 107)">File</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">level</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'debug'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token literal-property property">filename</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">join</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">logDirPath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'app.log'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token literal-property property">options</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">flags</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'a'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword control-flow" style="font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">isDev</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">add</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token keyword" style="font-style:italic">new</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 107)">winston</span><span class="token class-name punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token class-name" style="color:rgb(255, 203, 107)">transports</span><span class="token class-name punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token class-name" style="color:rgb(255, 203, 107)">Console</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token keyword module" style="font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">default</span><span class="token plain"> logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<blockquote>
<p>⚠️ This assumes you're transpiling ESM, because Electron does <a href="https://github.com/electron/electron/issues/21457" target="_blank" rel="noopener noreferrer">not yet support ESM</a>. Tip: You can use <a href="https://github.com/mifi/build-electron" target="_blank" rel="noopener noreferrer">build-electron</a> for this.</p>
</blockquote>
<p>Then use your logger:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token keyword module" style="font-style:italic">import</span><span class="token plain"> </span><span class="token imports">logger</span><span class="token plain"> </span><span class="token keyword module" style="font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'./logger.js'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">error</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'Some error happened'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> err</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<p>This will make your app log to <code>Library/Application\ Support/Your app/app.log</code> (on MacOS, and accordingly on other platforms). When running in development it will still log to your console.</p>
<p>Example outputs:</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">someObj</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.278Z info: { someObj: 1 }</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'test'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'a'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">b</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">2</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token literal-property property">c</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">3</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.281Z info: test 1 a { b: 2, c: 3 }</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'string'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.281Z info: string</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">a</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token literal-property property">b</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">c</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.281Z info: { a: 1, b: [ { c: [Array] } ] }</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'1'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'2'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">a</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.281Z info: 1 2 { a: 1 }</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">info</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">''</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.281Z info: [ {}, '' ]</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">error</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token keyword" style="font-style:italic">new</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 107)">Error</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'test1'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">additional</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'data'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'hmm'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.282Z error: Error: test1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.&lt;anonymous&gt; (app.js:39:14)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._compile (internal/modules/cjs/loader.js:968:30)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.Module._extensions..js (internal/modules/cjs/loader.js:986:10)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module.load (internal/modules/cjs/loader.js:816:32)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._load (internal/modules/cjs/loader.js:728:14)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Function.Module._load (electron/js2c/asar.js:748:26)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module.require (internal/modules/cjs/loader.js:853:19)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at require (internal/modules/cjs/helpers.js:74:18)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.&lt;anonymous&gt; (electron.js:9:16)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._compile (internal/modules/cjs/loader.js:968:30) { additional: 'data' } hmm</span><br></span></code></pre></div></div>
<p>.</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">logger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token method function property-access" style="color:rgb(130, 170, 255)">error</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'error occurred'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token keyword" style="font-style:italic">new</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 107)">Error</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(195, 232, 141)">'test2'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token literal-property property">additional</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'data'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">'hmm'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">2022-09-04T14:28:09.283Z error: error occurred test2 Error: test2</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.&lt;anonymous&gt; (app.js:40:32)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._compile (internal/modules/cjs/loader.js:968:30)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.Module._extensions..js (internal/modules/cjs/loader.js:986:10)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module.load (internal/modules/cjs/loader.js:816:32)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._load (internal/modules/cjs/loader.js:728:14)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Function.Module._load (electron/js2c/asar.js:748:26)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module.require (internal/modules/cjs/loader.js:853:19)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at require (internal/modules/cjs/helpers.js:74:18)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Object.&lt;anonymous&gt; (electron.js:9:16)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at Module._compile (internal/modules/cjs/loader.js:968:30) { additional: 'data' } hmm</span><br></span></code></pre></div></div>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="winston" term="winston"/>
        <category label="electron" term="electron"/>
        <category label="logger" term="logger"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Use Yarn for Elastic Beanstalk (or any other package manger than npm)]]></title>
        <id>https://mifi.no/blog/aws-eb-yarn/</id>
        <link href="https://mifi.no/blog/aws-eb-yarn/"/>
        <updated>2022-09-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Note: Moved here.]]></summary>
        <content type="html"><![CDATA[<p><strong>Note: <a href="https://mifi.no/docs/aws/elastic-beanstalk/">Moved here</a>.</strong></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="eb" term="eb"/>
        <category label="elasticbeanstalk" term="elasticbeanstalk"/>
        <category label="yarn" term="yarn"/>
        <category label="npm" term="npm"/>
        <category label="nodejs" term="nodejs"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[GitHub Actions to S3/CloudFront CI setup]]></title>
        <id>https://mifi.no/blog/github-actions-to-s3-cloudfront/</id>
        <link href="https://mifi.no/blog/github-actions-to-s3-cloudfront/"/>
        <updated>2022-09-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This article describes how to setup GitHub Actions CI deploy to AWS S3 / CloudFront.]]></summary>
        <content type="html"><![CDATA[<p>This article describes how to setup GitHub Actions CI deploy to AWS S3 / CloudFront.</p>
<p>Create an IAM user for GitHub Actions with a policy to allow deploying to the S3 bucket and invalidating the CloudFront distrubution:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token property">"Version"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"2012-10-17"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token property">"Statement"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Sid"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"AccessToGetBucketLocation"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Effect"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"Allow"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Action"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:GetBucketLocation"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Resource"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"arn:aws:s3:::my-bucket-name"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Sid"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"AccessToWebsiteBuckets"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Effect"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"Allow"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Action"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:PutBucketWebsite"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:PutObject"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:PutObjectAcl"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:GetObject"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:ListBucket"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"s3:DeleteObject"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Resource"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"arn:aws:s3:::my-bucket-name"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"arn:aws:s3:::my-bucket-name/*"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Sid"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"AccessToCloudfront"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Effect"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(195, 232, 141)">"Allow"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Action"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"cloudfront:GetInvalidation"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"cloudfront:CreateInvalidation"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token property">"Resource"</span><span class="token operator" style="color:rgb(137, 221, 255)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">              </span><span class="token string" style="color:rgb(195, 232, 141)">"arn:aws:cloudfront::012345678901:distribution/DISTRIBUTIONID"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></span></code></pre></div></div>
<p>Replace these variables:</p>
<ul>
<li><code>012345678901</code> -&gt; your AWS account ID</li>
<li><code>DISTRIBUTIONID</code> -&gt; your CloudFront distribution ID</li>
<li><code>my-bucket-name</code> -&gt; your S3 bucket's name</li>
</ul>
<p>In <a href="https://github.com/mifi/mifi.no/settings/secrets/actions" target="_blank" rel="noopener noreferrer">GitHub Actions Secrets</a>, set these environment variables:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">AWS_INVALIDATE_DISTRIBUTION_ID=DISTRIBUTIONID</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">AWS_S3_BUCKET=name-of-bucket</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">AWS_ACCESS_KEY_ID=***</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">AWS_SECRET_ACCESS_KEY=***</span><br></span></code></pre></div></div>
<p>In package.json add a deploy script:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">"deploy": "aws s3 sync --delete build/ \"s3://${AWS_S3_BUCKET}/\" --cache-control max-age=600 &amp;&amp; aws cloudfront create-invalidation --distribution-id \"${AWS_INVALIDATE_DISTRIBUTION_ID}\" --paths '/*'",</span><br></span></code></pre></div></div>
<p>Create <code>.github/workflows/deploy.yml</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">name: Deploy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">on:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  push:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    branches:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - master</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">jobs:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">  deploy:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    runs-on: ubuntu-latest</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    steps:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - uses: actions/checkout@v3</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - uses: actions/setup-node@v3</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        with:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          node-version: 16</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          cache: 'yarn'</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - uses: aws-actions/configure-aws-credentials@v1</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        with:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          aws-region: ${{ secrets.AWS_REGION }}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - run: yarn install --frozen-lockfile</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - run: yarn build</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">      - run: yarn deploy</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        env:</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">          AWS_INVALIDATE_DISTRIBUTION_ID: ${{ secrets.AWS_INVALIDATE_DISTRIBUTION_ID }}</span><br></span></code></pre></div></div>
<p>This assumes that the script <code>build</code> creates a directory <code>build</code> with all the static assets.</p>
<p>Now push the code and let GitHub Actions do its magic.</p>
<p>Example repo: <a href="https://github.com/mifi/mifi.no" target="_blank" rel="noopener noreferrer">https://github.com/mifi/mifi.no</a></p>
<p>Inspired by:</p>
<ul>
<li><a href="https://www.alexhyett.com/github-actions-deploy-to-s3" target="_blank" rel="noopener noreferrer">https://www.alexhyett.com/github-actions-deploy-to-s3</a></li>
</ul>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="github actions" term="github actions"/>
        <category label="s3" term="s3"/>
        <category label="cloudfront" term="cloudfront"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Example GitHub Action workflow to auto push a specified directory to a different GitHub repo]]></title>
        <id>https://mifi.no/blog/example-github-action-workflow-to-auto-push-a-specified-directory-to-a-different-github-repo/</id>
        <link href="https://mifi.no/blog/example-github-action-workflow-to-auto-push-a-specified-directory-to-a-different-github-repo/"/>
        <updated>2021-01-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I made an example GitHub Action workflow that will automatically push all files in a specified directory to a different GitHub repo using a GitHub deploy key. By enabling GitHub Pages for the destination repo, you can serve content dynamically generated by a GitHub action workflow/job.]]></summary>
        <content type="html"><![CDATA[<p>I made an example GitHub Action workflow that will automatically push all files in a specified directory to a <strong>different GitHub repo</strong> using a <a href="https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys" target="_blank" rel="noopener noreferrer">GitHub deploy key</a>. By enabling GitHub Pages for the destination repo, you can <strong>serve content dynamically generated by a GitHub action workflow/job.</strong></p>
<p><a href="https://github.com/mifi/github-action-push-static" target="_blank" rel="noopener noreferrer">Check it out!</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="use-cases">Use cases<a href="https://mifi.no/blog/example-github-action-workflow-to-auto-push-a-specified-directory-to-a-different-github-repo/#use-cases" class="hash-link" aria-label="Direct link to Use cases" title="Direct link to Use cases">​</a></h2>
<ul>
<li>Publish static (gatsby, jekyll etc.) site to separate GitHub repository</li>
<li>Publish build results to a web endpoint</li>
<li>Publish istanbul test coverage from unit/integration tests</li>
<li>Publish a custom badge like <a href="https://shields.io/" target="_blank" rel="noopener noreferrer">shields.io</a> (e.g a test coverage badge) <img decoding="async" loading="lazy" src="https://raw.githubusercontent.com/mifi/github-action-push-static/main/custom-badge/example.svg" alt="" class="img_ev3q"></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why">Why?<a href="https://mifi.no/blog/example-github-action-workflow-to-auto-push-a-specified-directory-to-a-different-github-repo/#why" class="hash-link" aria-label="Direct link to Why?" title="Direct link to Why?">​</a></h2>
<ul>
<li>Free serving of public websites with git history (public repositories don't cost anything)</li>
<li>Don't have to pay for services like <code>codecov.io</code> or <code>coveralls.io</code></li>
<li>Don't bloat your main repo with a long history of ever changing files.</li>
<li>Don't risk exposing any personal github token from a github action (a leak which which could give someone access to your whole GitHub account).</li>
</ul>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Hvordan Vippse til kontonummer]]></title>
        <id>https://mifi.no/blog/hvordan-vippse-til-kontonummer/</id>
        <link href="https://mifi.no/blog/hvordan-vippse-til-kontonummer/"/>
        <updated>2020-12-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Hvordan Vippse penger til et kontonummer i stedet for et telefonnummer.]]></summary>
        <content type="html"><![CDATA[<p>Hvordan Vippse penger til et kontonummer i stedet for et telefonnummer.</p>
<p>This article shows how to send money using the Vipps apps to an arbitrary bank account number instead of a phone number.</p>
<p>1 - Tap <strong>Scan</strong></p>
<p><img decoding="async" loading="lazy" src="https://static.mifi.no/dist/2022/IMG_3190.jpg" alt="" class="img_ev3q"></p>
<p>2 - Choose <strong>Bill</strong>
3 - Take a picture of anything (not a bill)</p>
<p><img decoding="async" loading="lazy" src="https://static.mifi.no/dist/2022/IMG_3191.jpg" alt="" class="img_ev3q"></p>
<p>4 - Choose <strong>Type in</strong></p>
<p><img decoding="async" loading="lazy" src="https://static.mifi.no/dist/2022/IMG_3192.jpg" alt="" class="img_ev3q"></p>
<p>5 - Manually enter the account number, message and amount and send the money</p>
<p><img decoding="async" loading="lazy" src="https://static.mifi.no/dist/2022/IMG_3193.jpg" alt="" class="img_ev3q"></p>
<p>6 - Now you know how 😎</p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[Sniffing SSL traffic on Android (Vipps Payment App)]]></title>
        <id>https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/</id>
        <link href="https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/"/>
        <updated>2020-12-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I previously attempted to do the same with an early version of the Vipps (Norwegian easy payment app), but the app has changed a lot since then. I decided to try again with the current version, and I learned that it has implemented quite a few new countermeasures preventing the old method from working.]]></summary>
        <content type="html"><![CDATA[<p>I <a href="https://mifi.no/blog/vipps-reverse-engineering/">previously attempted to do the same</a> with an early version of the Vipps (Norwegian easy payment app), but the app has changed a lot since then. I decided to try again with the current version, and I learned that it has implemented quite a few new countermeasures preventing the old method from working.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-process">The process<a href="https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/#the-process" class="hash-link" aria-label="Direct link to The process" title="Direct link to The process">​</a></h2>
<p>First download Vipps app on an Android phone, then connect it and copy the app over USB:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb shell pm list packages | grep vipps</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb shell pm path no.dnb.vipps</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb pull /data/app/no.dnb.vipps-QLoJ4giHA8cXj7Pl6RI20A==/base.apk</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb pull /data/app/no.dnb.vipps-QLoJ4giHA8cXj7Pl6RI20A==/split_config.arm64_v8a.apk</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb pull /data/app/no.dnb.vipps-QLoJ4giHA8cXj7Pl6RI20A==/split_config.xxhdpi.apk</span><br></span></code></pre></div></div>
<p>Note that app is split into 3 apk's. Now decompile the main APK:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">apktool -r d base.apk</span><br></span></code></pre></div></div>
<p>This will extract APK contents to <code>base</code>. I then used <a href="https://github.com/skylot/jadx" target="_blank" rel="noopener noreferrer">jaxd-gui</a> to decompile and inspect the (obfuscated) Java/smali source code.</p>
<p>Now we need to remove the code that checks the SSL certificates inside the <code>X509TrustManager</code>s. These can be found in <code>base/smali_classes3/util/h/bs/a.smali</code> and <code>base/smali_classes3/util/ma/c.smali</code>. Just return <code>void</code> early:</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">.method public checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;)V</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">+   return-void</span><br></span></code></pre></div></div>
<p>Next, inside the file <code>base/smali/com/dnb/vipps/android/common/networking/h.smali</code> there is extra certificate pinning code, we need to remove that:</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">    public final l.h a() {</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        h.a aVar = new h.a();</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("api.vipps.no", "sha256/nTNUIFsFUeN3c+hYZzlJfKRIwoRtRDyY6sVGPEauJWY=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("api.vipps.no", "sha256/O+qzlJSyoBSdYsSMceGYjjRqDxKVzUFjX0ymP+AC0f0=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("*.api.vipps.no", "sha256/nTNUIFsFUeN3c+hYZzlJfKRIwoRtRDyY6sVGPEauJWY=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("*.api.vipps.no", "sha256/O+qzlJSyoBSdYsSMceGYjjRqDxKVzUFjX0ymP+AC0f0=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("ece46ec4-6f9c-489b-8fe5-146a89e11635.tech-02.net", "sha256/Fig44L8aGm+MmceSD17e15lV7pWlsDvgViYUSaj/mqM=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">-       aVar.a("*.ece46ec4-6f9c-489b-8fe5-146a89e11635.tech-02.net", "sha256/Fig44L8aGm+MmceSD17e15lV7pWlsDvgViYUSaj/mqM=");</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        return aVar.b();</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    }</span><br></span></code></pre></div></div>
<p>Next, the Android operating system provides an additional network security config. Add to <code>application</code> in <code>base/AndroidManifest.xml</code>:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(255, 85, 114)">application</span><span class="token tag" style="color:rgb(255, 85, 114)"> </span><span class="token tag attr-name" style="color:rgb(255, 203, 107)">...</span><span class="token tag" style="color:rgb(255, 85, 114)"> </span><span class="token tag attr-name namespace" style="color:rgb(178, 204, 214)">android:</span><span class="token tag attr-name" style="color:rgb(255, 203, 107)">networkSecurityConfig</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(255, 85, 114)">@xml/network_security_config</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><br></span></code></pre></div></div>
<p>Then create a new file <code>base/res/xml/network_security_config.xml</code> with the contents:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token prolog">&lt;?xml version="1.0" encoding="utf-8"?&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(255, 85, 114)">network-security-config</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(255, 85, 114)">base-config</span><span class="token tag" style="color:rgb(255, 85, 114)"> </span><span class="token tag attr-name" style="color:rgb(255, 203, 107)">cleartextTrafficPermitted</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(255, 85, 114)">true</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(255, 85, 114)">trust-anchors</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">            </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(255, 85, 114)">certificates</span><span class="token tag" style="color:rgb(255, 85, 114)"> </span><span class="token tag attr-name" style="color:rgb(255, 203, 107)">src</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(255, 85, 114)">user</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag" style="color:rgb(255, 85, 114)"> </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">/&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">        </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(255, 85, 114)">trust-anchors</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(255, 85, 114)">base-config</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(255, 85, 114)">network-security-config</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><br></span></code></pre></div></div>
<p>Now recompile the modified APK:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">apktool b base -o vipps-modified.apk</span><br></span></code></pre></div></div>
<p>Now create a debug signing certificate for signing the APKs with:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo y | keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "cn=Mark Jones, ou=JavaSoft, o=Sun, c=US"</span><br></span></code></pre></div></div>
<p>Re-sign <strong>all APKs</strong> using the same debug keystore just created:</p>
<div class="language-sh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sh codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo android | apksigner sign --ks debug.keystore --out vipps-signed.apk vipps-modified.apk</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo android | apksigner sign --ks debug.keystore --out split_config.arm64_v8a-signed.apk split_config.arm64_v8a.apk</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">echo android | apksigner sign --ks debug.keystore --out split_config.xxhdpi-signed.apk split_config.xxhdpi.apk</span><br></span></code></pre></div></div>
<p>Install all the new APKs to the Android phone</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">adb install-multiple vipps-signed.apk split_config.arm64_v8a-signed.apk split_config.xxhdpi-signed.apk</span><br></span></code></pre></div></div>
<p>Run <a href="https://www.charlesproxy.com/" target="_blank" rel="noopener noreferrer">Charles proxy</a> with SSL proxying and add the following domains to <code>includes</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">api.vipps.no</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">login.vipps.io</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">*.bankidnorge.no</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">*.bankidapis.no</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">*.bankid.no</span><br></span></code></pre></div></div>
<p>From Charles, export the generated root certificate and download it to your Android phone and trust it for all apps/browser.</p>
<p>On the Android phone, launch</p>
<p>Start the app and start sniffing SSL traffic!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="next-steps--observations">Next steps / observations<a href="https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/#next-steps--observations" class="hash-link" aria-label="Direct link to Next steps / observations" title="Direct link to Next steps / observations">​</a></h2>
<p>All Vipps related requests from startup, OTP, BankID auth and setting PIN as well as logging in:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/register</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/u-security/v1/generateotp/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/u-security/v1/validateotp/</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/token</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/bankid-auth/v1/bankid/url?login_hint=BID</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://login.vipps.io/vinx?login_hint=BID&amp;requestId=...&amp;session=...&amp;target=https%3A%2F%2Fvipps.no%2F</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://login.vipps.io/bid/oidc/callback?state=vinx%3A...&amp;session_state=...&amp;code=...</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://login.vipps.io/vinx/callback?state=vinx%3A...&amp;session_state=...&amp;code=...</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://oidc.bankidapis.no/auth/realms/prod/precheck/auth?client_id=Vipps-BankID-Prod&amp;login_hint=BID&amp;nonce=...&amp;redirect_uri=https%3A%2F%2Flogin.vipps.io%2Fbid%2Foidc%2Fcallback&amp;response_type=code&amp;scope=openid+profile+nnin_altsub&amp;state=vinx%3A...&amp;ui_locales=en</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/token</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/passphrase/srp/pin</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/login/srp/init</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/app/security/v1/login/srp/pin/verify</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/u-security/v1/vinxlogin</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/a-profile/v1/details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/customer/fetchblockedusers</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/merchantTransaction/v1/dueList</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/utility/v1/configParams</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/oneclick/v1/pendingRequests</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/invoice/v2/invoices_v3</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/a-profile/v1/details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/payment/api/v1/agreement?includeCharges=true&amp;status=PENDING</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/a-profile/v1/details</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/a-profile/v1/consents/consentcard</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">https://api.vipps.no/vipps-1.8/vippslogin/v1/vippslogintoken</span><br></span></code></pre></div></div>
<ul>
<li>I'm stuck at the salting/hashing algorith of the PIN when creating/authenticating with a PIN. Seems like the app uses the <a href="https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol" target="_blank" rel="noopener noreferrer">Secure Remote Password protocol (SRP)</a></li>
<li>Seems like it uses <a href="https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets" target="_blank" rel="noopener noreferrer">JSON Web Key</a> for the initial <code>/app/security/v1/register</code> <code>deviceKeys</code>  call.</li>
<li>SRP hashes and salt seems to be encoded using (base64url)[<a href="https://github.com/joaquimserafim/base64-url" target="_blank" rel="noopener noreferrer">https://github.com/joaquimserafim/base64-url</a>]</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="other-reverse-engineering-tips">Other reverse engineering tips<a href="https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/#other-reverse-engineering-tips" class="hash-link" aria-label="Direct link to Other reverse engineering tips" title="Direct link to Other reverse engineering tips">​</a></h2>
<ul>
<li>Dump resources (strings/translations): <code>aapt dump resources base.apk | less</code></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="references">References<a href="https://mifi.no/blog/sniffing-ssl-traffic-on-android-vipps-payment-app/#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References">​</a></h2>
<ul>
<li><a href="https://developer.android.com/training/articles/security-config" target="_blank" rel="noopener noreferrer">https://developer.android.com/training/articles/security-config</a></li>
<li><a href="https://medium.com/@SwiftSafe/bypassing-and-disabling-ssl-pinning-on-android-to-perform-man-in-the-middle-attack-1ef18f5a453a" target="_blank" rel="noopener noreferrer">https://medium.com/@SwiftSafe/bypassing-and-disabling-ssl-pinning-on-android-to-perform-man-in-the-middle-attack-1ef18f5a453a</a></li>
<li><a href="https://medium.com/@hojat.sajadinia/bypass-okhttp-certificatepinner-on-android-9a45ad80a58b" target="_blank" rel="noopener noreferrer">https://medium.com/@hojat.sajadinia/bypass-okhttp-certificatepinner-on-android-9a45ad80a58b</a></li>
</ul>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
    </entry>
    <entry>
        <title type="html"><![CDATA[EDITLY - Slick, declarative command line video editing]]></title>
        <id>https://mifi.no/blog/editly-slick-declarative-command-line-video-editing/</id>
        <link href="https://mifi.no/blog/editly-slick-declarative-command-line-video-editing/"/>
        <updated>2020-04-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Editly is a tool and framework for declarative NLE (non-linear video editing) using Node.js and ffmpeg. It allows you to easily and programmatically create a video from set of clips, images and titles, with smooth transitions between.]]></summary>
        <content type="html"><![CDATA[<p>Editly is a tool and framework for declarative NLE (non-linear video editing) using Node.js and ffmpeg. It allows you to <strong>easily and programmatically</strong> create a video from set of clips, images and titles, with smooth transitions between.</p>
<p>There is a simple command line for quickly assembling a video from a set of clips or images, or you can use it from Javascript!</p>
<p><a href="https://youtu.be/LNeclLkxUEY" target="_blank" rel="noopener noreferrer"><img decoding="async" loading="lazy" src="https://github.com/mifi/gifs/raw/master/commonFeatures.gif" alt="demo" class="img_ev3q"></a></p>
<p>This GIF/youtube was created with this command: "editly <a href="https://github.com/mifi/editly/blob/master/examples/commonFeatures.json5" target="_blank" rel="noopener noreferrer">commonFeatures.json5</a>"</p>
<p>See <a href="https://github.com/mifi/editly/tree/master/examples#examples" target="_blank" rel="noopener noreferrer">more examples here</a></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="opensource video editor" term="opensource video editor"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Quickly identify minified React Native / web production stacktraces with stacktracify]]></title>
        <id>https://mifi.no/blog/tool-to-make-unreadable-production-stack-traces-readable-using-source-maps/</id>
        <link href="https://mifi.no/blog/tool-to-make-unreadable-production-stack-traces-readable-using-source-maps/"/>
        <updated>2020-04-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Error in production? 😨]]></summary>
        <content type="html"><![CDATA[<p>Error in production? 😨</p>
<p>Stack trace looks like this? 😱</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">TypeError h is not a function. (In 'h()', 'h' is undefined) </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:954:5353 </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:112:423 p</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:112:1740 </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:112:423 p</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:112:898 n</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:112:1273 </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:50:205 c</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:50:1623 b</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    main.jsbundle:50:488 _</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [native code] value</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    [native code] value</span><br></span></code></pre></div></div>
<p>...perhaps from production from a minified web JS bundle or a React Native error report in a tool like Bugsnag, Sentry or Crashlytics</p>
<p>No source map yet uploaded to bugsnag? 😰</p>
<p><strong>stacktracify</strong> takes a source map and a stack trace from <strong>your clipboard</strong> (or from a file) and outputs a readable stacktrace with proper line numbers for each line**</p>
<p>Example output:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">TypeError h is not a function. (In 'h()', 'h' is undefined) </span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at getAuthToken (logic/api.js:67:20)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at authRequest (logic/api.js:127:8)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at data (logic/SaveQueue.js:30:20)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at op (logic/SaveQueue.js:43:29)</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">    at __callImmediates (node_modules/react-native/Libraries/BatchedBridge/MessageQueue.js:143:11)</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-do-this-">How to do this? 😰<a href="https://mifi.no/blog/tool-to-make-unreadable-production-stack-traces-readable-using-source-maps/#how-to-do-this-" class="hash-link" aria-label="Direct link to How to do this? 😰" title="Direct link to How to do this? 😰">​</a></h2>
<p><strong>Copy the minified stack trace to clipboard, then run:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#bfc7d5;--prism-background-color:#292d3e"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#bfc7d5;background-color:#292d3e"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#bfc7d5"><span class="token plain">npm i -g stacktracify</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">react-native bundle --platform ios --entry-file index.js --dev false --bundle-output ios-release.bundle --sourcemap-output ios-release.bundle.map</span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#bfc7d5"><span class="token plain">stacktracify ios-release.bundle.map</span><br></span></code></pre></div></div>
<p>Now sit back and relax, analyze the stack trace 😅 and fix the bug 😎💯</p>
<p>Read more: <a href="https://github.com/mifi/stacktracify" target="_blank" rel="noopener noreferrer">https://github.com/mifi/stacktracify</a></p>]]></content>
        <author>
            <name>Mikael Finstad</name>
            <uri>https://github.com/mifi</uri>
        </author>
        <category label="stacktrace development node" term="stacktrace development node"/>
    </entry>
</feed>