<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Phil Nash</title><description>Phil Nash is a developer, speaker, blogger, developer advocate, sausage dog owner and beer lover.</description><link>https://philna.sh/</link><atom:link href="https://philna.sh/feed.xml" rel="self" type="application/rss+xml"/><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><lastBuildDate>Sun, 08 Mar 2026 02:50:21 GMT</lastBuildDate><generator>Astro v5.18.0</generator><item><title>5 quick tips for giving better presentations</title><link>https://philna.sh/blog/2026/03/05/talking-tips/</link><guid isPermaLink="true">https://philna.sh/blog/2026/03/05/talking-tips/</guid><description>&lt;p&gt;I have been &lt;a href=&quot;/speaking&quot;&gt;speaking publicly&lt;/a&gt; at developer conferences for over a decade and in that time I&apos;ve seen plenty of other people giving talks. Everyone gives talks differently, and if you are a speaker or thinking about getting into it, I encourage you to work on developing your unique style as well as your content.&lt;/p&gt;
&lt;p&gt;However, recently I have noticed a few little things that some speakers do and others do not that make for a better experience for both the audience and the presenter. They are mostly inconsequential for the content of a talk, but they can act as speed bumps that take the momentum out of a talk.&lt;/p&gt;
&lt;p&gt;So, in no particular order, here are a few things you should remember when giving a talk.&lt;/p&gt;
&lt;h2&gt;Finish strong&lt;/h2&gt;
&lt;p&gt;Have you ever seen this situation play out?&lt;/p&gt;
&lt;p&gt;A speaker wraps up their talk nicely with an insightful conclusion. Then they say something like, &quot;Thank you very much. Any questions?&quot;&lt;/p&gt;
&lt;p&gt;The audience, prepared to bring the house down with cheering and clapping are now caught like a deer in headlights. Instead of rapturous applause an awkward silence descends. Someone puts their hand up and asks a question. Q&amp;amp;A ensues, but everything now feels a bit flat.&lt;/p&gt;
&lt;p&gt;As an audience member, you want to show appreciation for a great job, so as a speaker you should give space for that. When concluding a talk, end with a &quot;Thank you&quot; or some other definitive way to indicate you are finished speaking, and let the crowd have their moment to thank you. Once the applause dies down, that&apos;s when you, or even better an MC or moderator, can indicate that it is time (or not) for questions.&lt;/p&gt;
&lt;p&gt;Don&apos;t let a talk fizzle out into the Q&amp;amp;A portion, finish on a high. Deal with the questions afterwards.&lt;/p&gt;
&lt;h2&gt;Take your lanyard off&lt;/h2&gt;
&lt;p&gt;The conference lanyard is the sign that you are part of this temporary tribe that has formed. When wearing it, you can speak to anyone else also adorned in the conference colours knowing you have some common ground.&lt;/p&gt;
&lt;p&gt;When you are on stage to deliver a talk, you no longer need the lanyard to signify you are part of the experience. But that&apos;s not why you should take it off. You should remove it for the duration of your talk simply because it gets in the way.&lt;/p&gt;
&lt;p&gt;On stage there is more to consider. A lanyard hanging around your neck may get in the way of your laptop if you need to use it, it might bother the microphone cable, it might just not look that great. You probably didn&apos;t choose your outfit based on the conference colour scheme, so why throw a dangling colour clash into the mix?&lt;/p&gt;
&lt;p&gt;Take your lanyard off for the talk, but don&apos;t forget to put it back on afterwards. You will want to help people remember who you are once you&apos;re no longer in the spotlight.&lt;/p&gt;
&lt;h2&gt;Keyboard shortcuts you wish you&apos;d known for years&lt;/h2&gt;
&lt;p&gt;There are two macOS keyboard shortcuts that I think are vital to know if you want to make smooth transitions between parts of your talk.&lt;/p&gt;
&lt;h3&gt;Cmd + F1&lt;/h3&gt;
&lt;p&gt;Let&apos;s say you&apos;re presenting in Keynote and you&apos;re using the presenter view; you can see the notes on your laptop, the audience sees the slides on the screen. Your laptop is using the external screen as an extended display. Now you want to demo something. To do this you need to switch to mirroring, so that you can see the same demo on your laptop as the audience is seeing on the screen.&lt;/p&gt;
&lt;p&gt;You could make this switch by opening the display settings and changing the setting. However, the keyboard shortcut is &lt;code&gt;Cmd + F1&lt;/code&gt;. Flipping between mirroring and extending with a keypress is quicker, smoother and keeps the energy in your presentation. &lt;code&gt;Cmd + F1&lt;/code&gt;, commit it to memory.&lt;/p&gt;
&lt;h3&gt;Cmd + Shift + F&lt;/h3&gt;
&lt;p&gt;If you&apos;re using Chrome and you want to go into fullscreen (with the green button in the top left of the window, or &lt;code&gt;Shift + Fn + F&lt;/code&gt;) and you find that the address bar is still showing, taking up valuable screen real estate, that can be fixed. &lt;code&gt;Cmd + Shift + F&lt;/code&gt; toggles whether the address bar shows. You&apos;re probably reading this in Chrome right now, try it out!&lt;/p&gt;
&lt;h2&gt;Resize the font, don&apos;t zoom the window&lt;/h2&gt;
&lt;p&gt;If you plan to live code or just show code within an IDE as part of a demo, you should make sure that the font is readable by the audience. I recommend doing this as part of a tech check before you are supposed to go on stage. This gives you the time to check things yourself and resize the text correctly.&lt;/p&gt;
&lt;p&gt;You might think that zooming in, with the shortcut &lt;code&gt;Cmd and +&lt;/code&gt; will help the audience see the code. In VS Code and other similar editors, that zooms the text and the entire interface. By the time the text is of a size that the audience will be able to read it, it will be crowded out by the file explorer and the terminal.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Instead, take the time to open the settings (&lt;code&gt;Cmd + ,&lt;/code&gt;) and change the font size to something readable. That way the rest of the interface stays out of the way. If you plan to show things in the built-in terminal, make sure to increase the terminal font size too, it&apos;s a different setting.&lt;/p&gt;
&lt;p&gt;Walk to the back of the room, or get a friend to check and give you a thumbs up, and make sure things are readable before you start the talk.&lt;/p&gt;
&lt;h2&gt;Trust in the tech&lt;/h2&gt;
&lt;p&gt;If you are in the fortunate position to be speaking at an event with a crew looking after you on stage, they will likely give you a microphone before you go on and be in control of it. Most of the time this means that the microphone is on and you needn&apos;t touch it, but it is muted at the sound desk. Then, when it is time for you to start they will unmute you and everyone will hear you.&lt;/p&gt;
&lt;p&gt;It can be unnerving to believe that once you start speaking everyone will be able to hear you, but you should. Much like my first tip to finish strong, you also want to start strong, and &quot;Hello, can you hear me? Is this on?&quot; is not the way to achieve that.&lt;/p&gt;
&lt;p&gt;Instead start by introducing yourself, start with a joke, start by thanking the audience for showing up. However you want to start, assume you will be heard and confidently start speaking. If something does go wrong, it&apos;s not your fault (unless you turned your microphone off on purpose). When things go right, you will capture the audience&apos;s attention and kick your session off in style.&lt;/p&gt;
&lt;h2&gt;Small tips, big impact&lt;/h2&gt;
&lt;p&gt;I have, of course, done the opposite of all of these things myself. I&apos;ve started by asking if I can be heard, zoomed my editor until only the side panel was visible, fiddled in settings and menus to make my screen show the right thing, flapped my lanyard around on stage, and finished with &quot;Thank you, any questions?&quot; and a silent room. I only hope by sharing some of these tips that you can avoid all of those things yourself, sail through your talk with all your energy being used to wow the audience, and feel great at the end of it all.&lt;/p&gt;
&lt;p&gt;So remember your shortcuts, take that lanyard off, trust that you will be heard, get your font sizes right, start with confidence and finish strong. I can&apos;t wait to see what you are going to talk about.&lt;/p&gt;
</description><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><category>speaking</category><category>devrel</category><category>conferences</category></item><item><title>Things you need to do for npm trusted publishing to work</title><link>https://philna.sh/blog/2026/01/28/trusted-publishing-npm/</link><guid isPermaLink="true">https://philna.sh/blog/2026/01/28/trusted-publishing-npm/</guid><description>&lt;p&gt;After the recent supply chain attacks on the npm ecosystem, notaby the &lt;a href=&quot;https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/&quot;&gt;Shai-Hulud 2.0 worm&lt;/a&gt;, &lt;a href=&quot;https://github.blog/security/supply-chain-security/our-plan-for-a-more-secure-npm-supply-chain/&quot;&gt;GitHub took a number of actions&lt;/a&gt; to shore up the security of publishing packages to hopefully avoid further attacks. One of the outcomes was that long-lived npm tokens were revoked in favour of short-lived tokens or using trusted publishing.&lt;/p&gt;
&lt;p&gt;I have GitHub Actions set up to publish new versions of npm packages that I maintain when a new tag is pushed to the repository. This workflow used long-lived npm tokens to authenticate, so when it came to updating a package recently I needed to update the publishing method too. The &lt;a href=&quot;https://docs.npmjs.com/trusted-publishers&quot;&gt;npm documentation on trusted publishing for npm packages&lt;/a&gt; was useful to a point, but there were some things I needed to do that the docs either didn&apos;t cover explicitly or weren&apos;t obvious enough, to get my package published successfully. I also came across &lt;a href=&quot;https://github.com/npm/cli/issues/8730&quot;&gt;this thread on GitHub where other people had similar issues&lt;/a&gt;. I wanted to share those things here.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;Briefly, the changes that worked for me were to add the following to my GitHub Action publishing workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Permission to generate an OIDC token
permissions:
  id-token: write

jobs:
  publish:
    steps:
      ...
      # Ensure the latest npm is installed
      - run: npm install -g npm@latest
      ...
      # Add the --provenance flag to the publish command
      - run: npm publish --provenance
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And ensure that the &lt;em&gt;package.json&lt;/em&gt; refers to the correct repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  ...
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/${username}/${packageName}.git&quot;,
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a bit more detail and alternative ways to set some of these settings, read on.&lt;/p&gt;
&lt;h2&gt;Package settings&lt;/h2&gt;
&lt;p&gt;Ok, so this is embarrassing, but initially I couldn&apos;t find the settings I needed to enable trusted publishing. The npm docs say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Navigate to your package settings on npmjs.com and find the &quot;Trusted Publisher&quot; section.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I spent far too long looking around the &lt;code&gt;https://www.npmjs.com/settings/${username}/packages&lt;/code&gt; page for the &quot;Trusted Publisher&quot; section. What I needed was the specific package settings, available here: &lt;code&gt;https://www.npmjs.com/package/${packageName}/access&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need to set up trusted publishing for each of your packages individually. That might be fine if you only maintain a few, it&apos;s going to be a huge hassle if you have a lot.&lt;/p&gt;
&lt;p&gt;Once you have filled in the trusted publisher settings, then its on to updating your project so that it can be published successfully.&lt;/p&gt;
&lt;h2&gt;Permissions&lt;/h2&gt;
&lt;p&gt;This is in the npm docs, so I&apos;m just including it for completeness. You need to &lt;a href=&quot;https://docs.npmjs.com/trusted-publishers#github-actions-configuration&quot;&gt;give the workflow permission to generate an OIDC token&lt;/a&gt; that it can then use to publish the package. To do this requires one permission being set in your workflow file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;permissions:
  id-token: write
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;npm version&lt;/h2&gt;
&lt;p&gt;The docs clearly call out that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Trusted publishing requires &lt;a href=&quot;https://docs.npmjs.com/cli/v11&quot;&gt;npm CLI&lt;/a&gt; version 11.5.1 or later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I needed to upgrade the version of npm used by my GitHub Actions workflow, so I added a simple step to install the latest version of npm as part of the run before publishing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- run: npm install -g npm@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Automatic provenance&lt;/h2&gt;
&lt;p&gt;The docs also say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When you publish using trusted publishing, npm automatically generates and publishes &lt;a href=&quot;https://docs.npmjs.com/generating-provenance-statements&quot;&gt;provenance attestations&lt;/a&gt; for your package. This happens by default—you don&apos;t need to add the --provenance flag to your publish command.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I did not find this to be the case. I needed to add the &lt;code&gt;--provenance&lt;/code&gt; flag so that my package would publish successfully.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;run: npm publish --provenance
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was something that &lt;a href=&quot;https://github.com/npm/cli/issues/8730#issuecomment-3538909998&quot;&gt;seemed to help others&lt;/a&gt; too. You may only need to pass &lt;code&gt;--provenance&lt;/code&gt; the first time, with it continuing to work automatically beyond that, but it can&apos;t hurt to keep it in your publish script (for when you need to update another package and you copy things over).&lt;/p&gt;
&lt;p&gt;You can also set your package to generate provenance attestations on publishing by setting the &lt;a href=&quot;https://docs.npmjs.com/cli/v11/using-npm/config#provenance&quot;&gt;&lt;code&gt;provenance&lt;/code&gt; option in &lt;code&gt;publishConfig&lt;/code&gt;&lt;/a&gt; in your &lt;em&gt;package.json&lt;/em&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  ...
  &quot;publishConfig&quot;: {
    &quot;provenance&quot;: true
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can set the &lt;code&gt;NPM_CONFIG_PROVENANCE&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;env:
  NPM_CONFIG_PROVENANCE: true
run: npm publish
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Repository details&lt;/h2&gt;
&lt;p&gt;Finally, I don&apos;t know if this last part helped as I did already have it set, but &lt;a href=&quot;https://github.com/npm/cli/issues/8730#issuecomment-3544738786&quot;&gt;others in this GitHub thread&lt;/a&gt; found that setting the &lt;a href=&quot;https://docs.npmjs.com/cli/v11/configuring-npm/package-json#repository&quot;&gt;&lt;code&gt;repository&lt;/code&gt; field in the package&apos;s &lt;em&gt;package.json&lt;/em&gt;&lt;/a&gt; to specifically point to the GitHub repository also helped.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  ...
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/${username}/${packageName}.git&quot;,
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you set up your trusted publisher in npm you do have to provide the repository details, so it makes sense to me that the package should agree with those details too.&lt;/p&gt;
&lt;h2&gt;Keep the ecosystem safe&lt;/h2&gt;
&lt;p&gt;Short-lived tokens, trusted publishing, and provenance all help keep the entire ecosystem safe. If you&apos;ve read this far, it is because you are also updating your packages to publish with this method&lt;/p&gt;
&lt;p&gt;I know there are people out there with many more packages, and packages that are much more popular than any of mine, but I hope this helps. It does amuse me that I went through this for &lt;a href=&quot;https://www.npmjs.com/package/@philnash/resend&quot;&gt;a package that I&apos;m pretty sure I&apos;m the only user of&lt;/a&gt;, but at least I now know how to do it for the future.&lt;/p&gt;
&lt;p&gt;I hope to see trusted publishing continue to expand to more providers, it is limited to GitHub and GitLab at the time of writing, and to be used by more packages. And I hope to see fewer worms charging through the package ecosystem and threatening all of our applications in the future.&lt;/p&gt;
</description><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><category>javascript</category><category>npm</category></item><item><title>How wrong can a JavaScript Date calculation go?</title><link>https://philna.sh/blog/2026/01/11/javascript-date-calculation/</link><guid isPermaLink="true">https://philna.sh/blog/2026/01/11/javascript-date-calculation/</guid><description>&lt;p&gt;The &lt;code&gt;Date&lt;/code&gt; object in JavaScript is frequently one that causes trouble. So much so, it is set to be replaced by &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal&quot;&gt;&lt;code&gt;Temporal&lt;/code&gt;&lt;/a&gt; soon. This is the story of an issue that I faced that will be much easier to handle once &lt;code&gt;Temporal&lt;/code&gt; is more widespread.&lt;/p&gt;
&lt;h2&gt;The issue&lt;/h2&gt;
&lt;p&gt;In January 2025 I was in Santa Clara, California writing some JavaScript to perform some reporting. I wanted to be able to get a number of events that happened within a month, so I would create a date object for the first day of the month, add one month to it and then subtract a day to return the last day. Seems straightforward, right?&lt;/p&gt;
&lt;p&gt;I got a really weird result though. I reduced the issue to the following code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const date = new Date(&quot;2024-01-01T00:00:00.000Z&quot;);
date.toISOString();
// =&amp;gt; &quot;2024-01-01T00:00:00.000Z&quot; as expected
date.setMonth(1);
date.toISOString();
// =&amp;gt; &quot;2023-03-04-T00:00:00.000Z&quot; WTF?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I added a month to the 1st of January 2024 and landed on the 4th March, 2023. What happened?&lt;/p&gt;
&lt;h2&gt;Times and zones&lt;/h2&gt;
&lt;p&gt;You might have thought it was odd for me to set this scene on the West coast of the US, but it turned out this mattered. This code would have run fine in UTC and everywhere East of it.&lt;/p&gt;
&lt;p&gt;JavaScript dates are more than just dates, they are responsible for time as well. Even though I only wanted to deal with days and months in this example the time still mattered.&lt;/p&gt;
&lt;p&gt;I did know this, so I set the time to UTC thinking that this would work for me wherever I was. That was my downfall. Let&apos;s break down what happened.&lt;/p&gt;
&lt;p&gt;Midnight on the 1st January, 2024 in UTC is still 4pm on the 31st December, 2023 in Pacific Time (UTC-8). &lt;code&gt;date.setMonth(1)&lt;/code&gt; sets the date to February (as months are 0-indexed unlike days). But we started on 31st December, 2023 so JavaScript has to handle the non-existant date of 31st February, 2023. It does this by overflowing to the next month, so we get 3rd March. Finally, to print it out, the date is translated back into UTC, giving the final result: midnight on 4th March, 2023.&lt;/p&gt;
&lt;p&gt;All of these steps feel reasonable when you break it down, the confusion stems from how unexpected that result was.&lt;/p&gt;
&lt;p&gt;So, how do you fix this?&lt;/p&gt;
&lt;h2&gt;Always use UTC&lt;/h2&gt;
&lt;p&gt;Since I didn&apos;t actually care for the time and I knew I wanted to work with UTC, I fixed this code using the &lt;code&gt;Date&lt;/code&gt; object&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCMonth&quot;&gt;&lt;code&gt;setUTCMonth&lt;/code&gt; method&lt;/a&gt;. My original code subtracted a day to get the last day in a month, so I used the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setUTCDate&quot;&gt;&lt;code&gt;setUTCDate&lt;/code&gt; method&lt;/a&gt; too. All &lt;code&gt;set${timePeriod}&lt;/code&gt; methods have a &lt;code&gt;setUTC${timePeriod}&lt;/code&gt; equivalent to help you work with this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const date = new Date(&quot;2024-01-01T00:00:00.000Z&quot;);
date.toISOString();
// =&amp;gt; &quot;2024-01-01T00:00:00.000Z&quot;
date.setUTCMonth(1);
date.toISOString();
// =&amp;gt; &quot;2024-02-01-T00:00:00.000Z&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So this fixed my issue. Can it be better though?&lt;/p&gt;
&lt;h2&gt;Bring on Temporal&lt;/h2&gt;
&lt;p&gt;One of the reasons this went wrong was because I was trying to manipulate dates, but I was actually manipulating dates and times without thinking about it. I mentioned &lt;code&gt;Temporal&lt;/code&gt; at the top of the post because it has objects specifically for this.&lt;/p&gt;
&lt;p&gt;If I was to write this code using &lt;code&gt;Temporal&lt;/code&gt; I would be able to use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/PlainDate&quot;&gt;&lt;code&gt;Temporal.PlainDate&lt;/code&gt;&lt;/a&gt; to represent a calendar date, a date without a time or time zone.&lt;/p&gt;
&lt;p&gt;This simplifies things already, but &lt;code&gt;Temporal&lt;/code&gt; also makes it more obvious how to manipulate dates. Rather than setting months and dates or adding milliseconds to update a date, you add a duration. You can either construct a duration with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Duration&quot;&gt;&lt;code&gt;Temporal.Duration&lt;/code&gt; object&lt;/a&gt; or use an object that defines a duration.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Temporal&lt;/code&gt; also makes objects immutable, so every time you change a date it returns a new object.&lt;/p&gt;
&lt;p&gt;In this case I wanted to add a month, so with &lt;code&gt;Temporal&lt;/code&gt; it would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const startDate = Temporal.PlainDate.from(&quot;2024-01-01&quot;);
// =&amp;gt; Temporal.PlainDate 2024-01-01
const nextMonth = startDate.add({ months: 1 });
// =&amp;gt; Temporal.PlainDate 2024-02-01
const endDate = nextMonth.subtract({ days: 1 });
// =&amp;gt; Temporal.PlainDate 2024-01-31
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Date manipulation without worrying about times, wonderful!&lt;/p&gt;
&lt;p&gt;Of course, there are many more benefits to the very well throught out &lt;code&gt;Temporal&lt;/code&gt; API and I cannot wait for it to be a part of every JavaScript runtime.&lt;/p&gt;
&lt;h2&gt;Mind the time zone&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Temporal&lt;/code&gt; has still not made it to many JavaScript engines. At the time of writing, it is available in Firefox and nowhere else, so if you want to test this out open up Firefox or check out one of the polyfills &lt;a href=&quot;https://github.com/js-temporal/temporal-polyfill&quot;&gt;@js-temporal/polyfill&lt;/a&gt; or &lt;a href=&quot;https://www.npmjs.com/package/temporal-polyfill&quot;&gt;temporal-polyfill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Edit:&amp;lt;/strong&amp;gt; Merely two days after publishing this, &amp;lt;a href=&quot;https://developer.chrome.com/blog/new-in-chrome-144#temporal&quot;&amp;gt;support for Temporal started rolling out in Chrome 144&amp;lt;/a&amp;gt;. According to &amp;lt;a href=&quot;https://caniuse.com/temporal&quot;&amp;gt;Can I Use, Temporal&amp;lt;/a&amp;gt; is available behind a flag in Safari Technical Preview, so we might be close there too.&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;If you still have to use &lt;code&gt;Date&lt;/code&gt; make sure you keep your time zone in mind. I&apos;d try to move to, or at least learn how to use, &lt;code&gt;Temporal&lt;/code&gt; now.&lt;/p&gt;
&lt;p&gt;And watch out for time zones, even when you try to avoid them they can end up giving you a headache.&lt;/p&gt;
</description><pubDate>Sun, 11 Jan 2026 00:00:00 GMT</pubDate><category>javascript</category></item><item><title>How to Create Vector Embeddings in Python</title><link>https://philna.sh/blog/2025/04/08/how-to-create-vector-embeddings-in-python/</link><guid isPermaLink="true">https://philna.sh/blog/2025/04/08/how-to-create-vector-embeddings-in-python/</guid><description>&lt;p&gt;When you’re building a &lt;a href=&quot;https://www.ibm.com/think/topics/retrieval-augmented-generation&quot;&gt;retrieval-augmented generation (RAG)&lt;/a&gt; app, the first thing you need to do is prepare your data. You need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collect your unstructured data&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/&quot;&gt;split it into chunks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;turn those chunks into &lt;a href=&quot;https://www.ibm.com/think/topics/vector-embedding&quot;&gt;vector embeddings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;store the embeddings in a vector database&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are many ways that you can create vector embeddings in Python. In this post, we’ll take a look at four ways to generate vector embeddings: locally, via API, via a framework, and with &lt;a href=&quot;https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html&quot;&gt;Astra DB&apos;s Vectorize&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This post was originally written for DataStax, but didn&apos;t survive a content migration as part of &amp;lt;a href=&quot;https://www.ibm.com/new/announcements/ibm-to-acquire-datastax-helping-clients-bring-the-power-of-unstructured-data-to-enterprise-ai-applications&quot;&amp;gt;IBM&apos;s purchase&amp;lt;/a&amp;gt;. I thought the content was useful, so have republished it here.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Local vector embeddings&lt;/h2&gt;
&lt;p&gt;There are many &lt;a href=&quot;https://huggingface.co/models?pipeline_tag=sentence-similarity&amp;amp;sort=trending&quot;&gt;pre-trained embedding models available on Hugging Face&lt;/a&gt; that you can use to create vector embeddings. &lt;a href=&quot;https://www.sbert.net/&quot;&gt;Sentence Transformers (SBERT)&lt;/a&gt; is a library that makes it easy to use these models for vector embedding, as well as cross-encoding for reranking. It even has tools for finetuning models, if that’s something that might be of use.&lt;/p&gt;
&lt;p&gt;You can install the library with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install sentence_transformers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A popular local model for vector embedding is &lt;a href=&quot;https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2&quot;&gt;all-MiniLM-L6-v2&lt;/a&gt;. It’s trained as a good all-rounder that produces a 384-dimension vector from a chunk of text.&lt;/p&gt;
&lt;p&gt;To use it, import &lt;code&gt;sentence_transformers&lt;/code&gt; and create a model using the identifier from Hugging Face, in this case &quot;all-MiniLM-L6-v2&quot;. If you want to use a model that isn&apos;t in the &lt;a href=&quot;https://huggingface.co/sentence-transformers/&quot;&gt;sentence-transformers project&lt;/a&gt;, like the multilingual &lt;a href=&quot;https://huggingface.co/BAAI/bge-m3&quot;&gt;BGE-M3&lt;/a&gt;, you can use the organization to identify the model too, like, &quot;BAAI/BGE-M3&quot;. Once you&apos;ve loaded the model, use the &lt;code&gt;encode&lt;/code&gt; method to create the vector embedding. The full code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sentence_transformers import SentenceTransformer


model = SentenceTransformer(&quot;all-MiniLM-L6-v2&quot;)
sentence = &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;
embedding = model.encode(sentence)

print(embedding)
# =&amp;gt; [ 1.95171311e-03  1.51085425e-02  3.36140348e-03  2.48030387e-02 ... ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you pass an array of texts to the model, they’ll all be encoded:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sentence_transformers import SentenceTransformer


model = SentenceTransformer(&quot;all-MiniLM-L6-v2&quot;)
sentences = [
    &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
    &quot;A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.&quot;,
    &quot;A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.&quot;,
]
embeddings = model.encode(sentences)

print(embeddings)
# =&amp;gt; [[ 0.00195174  0.01510859  0.00336139 ...  0.07971715  0.09885529  -0.01855042]
# [-0.04523939 -0.00046248  0.02036596 ...  0.08779042  0.04936493  -0.06218244]
# [-0.05453169  0.01125113 -0.00680178 ...  0.06443197  0.08771271  -0.00063468]]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are many &lt;a href=&quot;https://huggingface.co/models?pipeline_tag=feature-extraction&amp;amp;library=sentence-transformers&amp;amp;sort=trending&quot;&gt;more models you can use to generate vector embeddings with the sentence-transformers library&lt;/a&gt; and, because you’re running locally, you can try them out to see which is most appropriate for your data. You do need to watch out for any restrictions that these models might have. For example, the all-MiniLM-L6-v2 model doesn’t produce good results for more than 128 tokens and can only handle a maximum of 256 tokens. BGE-M3, on the other hand, can encode up to 8,192 tokens. However, the BGE-M3 model is a couple of gigabytes in size and all-MiniLM-L6-v2 is under 100MB, so there are space and memory constraints to consider, too.&lt;/p&gt;
&lt;p&gt;Local embedding models like this are useful when you’re experimenting on your laptop, or if you have &lt;a href=&quot;https://www.sbert.net/docs/installation.html#install-pytorch-with-cuda-support&quot;&gt;hardware that PyTorch can use to speed up the encoding process&lt;/a&gt;. It’s a good way to get comfortable running different models and seeing how they interact with your data.&lt;/p&gt;
&lt;p&gt;If you don&apos;t want to run your models locally, there are plenty of available APIs you can use to create embeddings for your documents.&lt;/p&gt;
&lt;h2&gt;APIs&lt;/h2&gt;
&lt;p&gt;There are several services that make embedding models available as APIs. These include LLM providers like &lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings&quot;&gt;OpenAI&lt;/a&gt;, &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/embeddings&quot;&gt;Google&lt;/a&gt;, or &lt;a href=&quot;https://docs.cohere.com/docs/cohere-embed&quot;&gt;Cohere&lt;/a&gt;, as well as specialist providers like &lt;a href=&quot;https://jina.ai/embeddings/&quot;&gt;Jina AI&lt;/a&gt; or model hosts like &lt;a href=&quot;https://docs.fireworks.ai/guides/querying-embeddings-models&quot;&gt;Fireworks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These API providers provide HTTP APIs, often with a Python package to make it easy to call them. You will typically require an API key from the service. Once you have that setup you can generate vector embeddings by sending your text to the API.&lt;/p&gt;
&lt;p&gt;For example, with Google&apos;s &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/sdks&quot;&gt;google-genai SDK&lt;/a&gt; and a Gemini API key you can generate a vector embedding with their &lt;a href=&quot;https://developers.googleblog.com/en/gemini-embedding-text-model-now-available-gemini-api/&quot;&gt;Gemini embedding model&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from google import genai


client = genai.Client(api_key=&quot;GEMINI_API_KEY&quot;)

result = client.models.embed_content(
        model=&quot;gemini-embedding-001&quot;,
        contents=&quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;)

print(result.embeddings)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each API can be different, though many providers do make OpenAI-compatible APIs. However, each time you try a new provider you might find you have a new API to learn. Unless, of course, you try one of the available frameworks that are intended to simplify this.&lt;/p&gt;
&lt;h2&gt;Frameworks&lt;/h2&gt;
&lt;p&gt;There are several projects available, like &lt;a href=&quot;https://www.langchain.com/&quot;&gt;LangChain&lt;/a&gt; or &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/&quot;&gt;LlamaIndex&lt;/a&gt;, that create abstractions over the common components of the GenAI ecosystem, including embeddings.&lt;/p&gt;
&lt;p&gt;Both LangChain and LlamaIndex have methods for creating vector embeddings via APIs or local models, all with the same interface. For example, you can create the same Gemini embedding as the code snippet above with LangChain like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain_google_genai import GoogleGenerativeAIEmbeddings


embeddings = GoogleGenerativeAIEmbeddings(
    model=&quot;gemini-embedding-001&quot;,
    google_api_key=&quot;GEMINI_API_KEY&quot;
)
result = embeddings.embed_query(&quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;)
print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a comparison, here is how you would generate an embedding using an OpenAI embeddings model and LangChain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain_openai import OpenAIEmbeddings


embeddings = OpenAIEmbeddings(
    model=&quot;text-embedding-3-small&quot;,
    api_key=&quot;OPENAI_API_KEY&quot;
)
result = embeddings.embed_query(&quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;)
print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We had to change the name of the import and the API key we used, but otherwise the code is identical. This makes it easy to swap them out and experiment.&lt;/p&gt;
&lt;p&gt;If you&apos;re using LangChain to build your entire RAG pipeline, these embeddings fit in well with the vector database interfaces. You can provide an embedding model to the database object and LangChain handles generating the embeddings as you insert documents or perform queries. For example, here&apos;s how you can combine the Google embeddings model with the &lt;a href=&quot;https://python.langchain.com/docs/integrations/vectorstores/astradb/&quot;&gt;LangChain wrapper for Astra DB&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from langchain_google_genai import GoogleGenerativeAIEmbeddings
from langchain_astradb import AstraDBVectorStore


embeddings = GoogleGenerativeAIEmbeddings(
    model=&quot;gemini-embedding-001&quot;,
    google_api_key=&quot;GEMINI_API_KEY&quot;
)

vector_store = AstraDBVectorStore(
   collection_name=&quot;astra_vector_langchain&quot;,
   embedding=embeddings,
   api_endpoint=&quot;ASTRA_DB_API_ENDPOINT&quot;,
   token=&quot;ASTRA_DB_APPLICATION_TOKEN&quot;
)

vector_store.add_documents(documents) # a list of document objects to store in the db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the same &lt;code&gt;vector_store&lt;/code&gt; object and associated embeddings to perform the vector search, too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;results = vector_store.similarity_search(&quot;Are robots allowed to protect themselves?&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LlamaIndex has a similar set of abstractions that enable you to combine different embedding models and vector stores. Check out this &lt;a href=&quot;https://docs.llamaindex.ai/en/stable/understanding/rag/&quot;&gt;LlamaIndex introduction to RAG&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;p&gt;If you&apos;re new to embeddings, &lt;a href=&quot;https://python.langchain.com/docs/integrations/text_embedding/&quot;&gt;LangChain&lt;/a&gt; has a handy list of embedding models and providers that can help you find different options to try.&lt;/p&gt;
&lt;h2&gt;Directly in the database&lt;/h2&gt;
&lt;p&gt;The methods we’ve talked through so far have involved creating a vector independently of storing it in or using it to search against a vector database. When you want to store those vectors in a &lt;a href=&quot;https://docs.datastax.com/en/astra-db-serverless/index.html&quot;&gt;vector database like Astra DB&lt;/a&gt;, it looks a bit like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from astrapy import DataAPIClient


client = DataAPIClient(&quot;ASTRA_DB_APPLICATION_TOKEN&quot;)
database = client.get_database(&quot;ASTRA_DB_API_ENDPOINT&quot;)
collection = database.get_collection(&quot;COLLECTION_NAME&quot;)

result = collection.insert_one(
    {
         &quot;text&quot;: &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
         &quot;$vector&quot;: [0.04574034, 0.038084425, -0.00916391, ...]
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above assumes that you have already created your vector-enabled collection with the right number of dimensions for the model you’re using.&lt;/p&gt;
&lt;p&gt;Performing a vector search then looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cursor = collection.find(
    {},
    sort={&quot;$vector&quot;: [0.04574034, 0.038084425, -0.00916391, ...]}
)

for document in cursor:
    print(document)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In these examples, you have to create your vectors first, before storing or searching against the database with them. In the case of the frameworks, you might not see this happen, as it has been abstracted away, but the operations are being performed.&lt;/p&gt;
&lt;p&gt;With Astra DB, you can have the database generate the vector embeddings for you as you either insert the document into the collection or at the point of performing the search. This is called &lt;a href=&quot;https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html&quot;&gt;Astra Vectorize&lt;/a&gt; and it simplifies a crucial step in your RAG pipeline.&lt;/p&gt;
&lt;p&gt;To use Vectorize, you first need to set up an embedding provider integration. There’s one built-in integration that you can use with no extra work; the &lt;a href=&quot;https://build.nvidia.com/nvidia/embed-qa-4&quot;&gt;NVIDIA NV-Embed-QA model&lt;/a&gt;, or you can choose one of the other embeddings providers and configure them with your API.&lt;/p&gt;
&lt;p&gt;When you create a collection, you can choose which embedding provider you want to use with the requisite number of dimensions.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;When you set up your collection this way you can add content and have it automatically vectorized by using the special property &lt;code&gt;$vectorize&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;result = collection.insert_one(
    {
         &quot;$vectorize&quot;: &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;
    }
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when a user query comes in, you can perform a vector search by sorting using the &lt;code&gt;$vectorize&lt;/code&gt; property. Astra DB will create the vector embedding and then make the search in one step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cursor = collection.find(
    {},
    sort={&quot;$vectorize&quot;: &quot;Are robots allowed to protect themselves?&quot;},
    limit=5
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are several advantages to this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Astra DB team has done the work to make the embedding creation robust already&lt;/li&gt;
&lt;li&gt;Making two separate API calls to create embeddings and then store them is often slower than letting Astra DB handle it&lt;/li&gt;
&lt;li&gt;Using the built-in NVIDIA embeddings model is even quicker than that&lt;/li&gt;
&lt;li&gt;You have less code to write and maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;A world of vector embedding options&lt;/h2&gt;
&lt;p&gt;As we have seen, there are many choices you can make in how to implement vector embeddings, which model you use, and which provider you use. It&apos;s an important step in your RAG pipeline and it is important to spend the time to find out which model and method is right for your application and your data.&lt;/p&gt;
&lt;p&gt;You can choose to host your own models, rely on third-party APIs, abstract the problem away through frameworks, or entrust Astra DB to create embeddings for you. Of course, if you want to avoid code entirely, then you can &lt;a href=&quot;https://langflow.org/&quot;&gt;drag-and-drop your components into place with Langflow&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><category>python</category><category>genai</category><category>vector embeddings</category></item><item><title>Troubles with multipart form data and fetch in Node.js</title><link>https://philna.sh/blog/2025/01/14/troubles-with-multipart-form-data-fetch-node-js/</link><guid isPermaLink="true">https://philna.sh/blog/2025/01/14/troubles-with-multipart-form-data-fetch-node-js/</guid><description>&lt;p&gt;This is one of those cathartic blog posts. One in which I spent several frustrating hours trying to debug something that really should have just worked. Once I had finally found out what was going on I felt that I had to write it all down just in case someone else is out there dealing with the same issue. So if you have found yourself in a situation where using &lt;code&gt;fetch&lt;/code&gt; in Node.js for a &lt;code&gt;multipart/form-data&lt;/code&gt; request doesn&apos;t work, this might help you out.&lt;/p&gt;
&lt;p&gt;If that doesn&apos;t apply to you, have a read anyway and share my pain.&lt;/p&gt;
&lt;h2&gt;What happened?&lt;/h2&gt;
&lt;p&gt;Today, while trying to write a Node.js client for an API, I got stuck on one particular endpoint. It was an endpoint for uploading files, so it required the body to be formatted as &lt;code&gt;multipart/form-data&lt;/code&gt;. JavaScript makes it easy to create such a request, you use a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData&quot;&gt;&lt;code&gt;FormData&lt;/code&gt;&lt;/a&gt; object to gather your data, including files, and you submit it via &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch&quot;&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt;. The formatting of the request body is then handled for you and it normally just works.&lt;/p&gt;
&lt;p&gt;Today it did not &quot;just work&quot;.&lt;/p&gt;
&lt;h2&gt;HTTP 422&lt;/h2&gt;
&lt;p&gt;Since version 18, Node.js has supported the &lt;code&gt;fetch&lt;/code&gt; API, via a project called &lt;a href=&quot;https://github.com/nodejs/undici&quot;&gt;undici&lt;/a&gt;. The undici project is added as a dependency to Node.js and the &lt;code&gt;fetch&lt;/code&gt; function is exposed to the global scope.&lt;/p&gt;
&lt;p&gt;To write the code for this upload endpoint should have been straightforward. I put together something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { readFile } from &quot;node:fs/promises&quot;;
import { extname, basename } from &quot;node:path&quot;;

async function uploadFile(url, filePath) {
  const data = await readFile(filePath);
  const type = mime.getType(extname(filePath));
  const file = new File([data], basename(filePath), type);

  const form = new FormData();
  form.append(&quot;file&quot;, file);

  const headers = new Headers();
  headers.set(&quot;Accept&quot;, &quot;application/json&quot;);

  return fetch(url, {
    method: &quot;POST&quot;,
    headers,
    body: form
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The real code has a few more complexities, but this is a good approximation of what I expected to be able to write.&lt;/p&gt;
&lt;p&gt;I lined up a test against the API, fired it off and was disappointed to receive a 422 response with the message &quot;Invalid multipart formatting&quot;.&lt;/p&gt;
&lt;p&gt;I pored back over the code, not that there was a lot of it, to try to work out what I had done wrong. Unable to find anything, I turned to other tools.&lt;/p&gt;
&lt;p&gt;I tried to proxy and inspect my request to see if anything was obviously wrong. Then I tried sending the request from another tool to see if I could get the API endpoint to respond with a success. Using &lt;a href=&quot;https://www.usebruno.com/&quot;&gt;Bruno&lt;/a&gt; I was able to make a successful request.&lt;/p&gt;
&lt;p&gt;With a correct request and an incorrect request, I compared the two. But I didn&apos;t get very far. The URL, the headers, and the request body all looked the same, yet one method of sending the request worked and the other didn&apos;t.&lt;/p&gt;
&lt;h2&gt;Digging into the API&lt;/h2&gt;
&lt;p&gt;The API client I am writing is for &lt;a href=&quot;https://www.langflow.org/&quot;&gt;Langflow&lt;/a&gt;. It&apos;s an open-source, low-code tool for building generative AI flows and agents. Langflow is part of &lt;a href=&quot;https://www.datastax.com/&quot;&gt;DataStax&lt;/a&gt;, where I am working and doing things like &lt;a href=&quot;https://www.datastax.com/blog/genai-bluesky-bot-with-langflow-typescript-node-js&quot;&gt;hooking Langflow up to Bluesky to create fun generative AI bots&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because Langflow is open-source, once I had run out of ideas with my code I could dig into the code behind the API to see if I could work out what was going on there. I found &lt;a href=&quot;https://github.com/langflow-ai/langflow/blob/e7a20051887cb1f86c2c736ab3651c13986b0869/src/backend/base/langflow/main.py#L186-L193&quot;&gt;where the error message was coming from along with a hint as to what might be wrong&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Multipart requests&lt;/h2&gt;
&lt;p&gt;A multipart request is often made up of multiple parts that may not be of the same type. This is how you are able to submit text fields and upload an image file in the same request. To separate the different types, a multipart request comes up with a unique string to act as a boundary between the types of content. This boundary string is shared in the &lt;code&gt;Content-Type&lt;/code&gt; header and looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: multipart/form-data; boundary=ExampleBoundaryString
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example multipart request would then look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=ExampleBoundaryString

--ExampleBoundaryString
Content-Disposition: form-data; name=&quot;description&quot;

Description input value
--ExampleBoundaryString
Content-Disposition: form-data; name=&quot;myFile&quot;; filename=&quot;foo.txt&quot;
Content-Type: text/plain

[content of the file foo.txt chosen by the user]
--ExampleBoundaryString--
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A server is then able to split up and parse the different parts of the request using the boundary.&lt;/p&gt;
&lt;p&gt;This &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_multipart_forms&quot;&gt;&lt;code&gt;Content-Type&lt;/code&gt; example is courtesy of MDN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Langflow API was checking to see whether the request body started with the boundary string and ended with the boundary string plus two dashes and then &lt;code&gt;\r\n&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;boundary_start = f&quot;--{boundary}&quot;.encode()
boundary_end = f&quot;--{boundary}--\r\n&quot;.encode()

if not body.startswith(boundary_start) or not body.endswith(boundary_end):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content={&quot;detail&quot;: &quot;Invalid multipart formatting&quot;},
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The combination of a carriage return and line feed (CRLF), a holdover from the days of typewriters, turned out to be my undoing. After throwing some &lt;code&gt;print&lt;/code&gt; lines in this code (I do not know how to debug Python any other way) I confirmed that the end of the body did not match because it was missing the CRLF.&lt;/p&gt;
&lt;p&gt;A missing CRLF. Hours of debugging trying to spot a missing &lt;code&gt;\r\n&lt;/code&gt;. A Python application causing me trouble over insignificant whitespace.&lt;/p&gt;
&lt;h2&gt;Convention over specification&lt;/h2&gt;
&lt;p&gt;It turns out that spec for &lt;code&gt;multipart/form-data&lt;/code&gt;, &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7578#page-4&quot;&gt;RFC 7578&lt;/a&gt; does not mandate that the body of the request ends with a CRLF. It does say that each different part of the request must be delimited with a CRLF, &quot;--&quot;, and then the value of the boundary parameter. It does not say that there needs to be a CRLF at the end of the body, nor does it say that there can&apos;t be a CRLF either.&lt;/p&gt;
&lt;p&gt;In fact, it turns out that many popular HTTP clients, &lt;a href=&quot;https://github.com/curl/curl/blob/3434c6b46e682452973972e8313613dfa58cd690/lib/mime.c#L1029-L1030&quot;&gt;including curl&lt;/a&gt;, do add this CRLF. It&apos;s a bit of a convention in HTTP clients, so it seems that when the team at Langflow wanted to implement a check on the validity of a multipart request, they included the CRLF in their expectations.&lt;/p&gt;
&lt;p&gt;On the other hand, I can only presume the team building undici looked at the spec and realised they didn&apos;t need to add unnecessary whitespace and left the CRLF out.&lt;/p&gt;
&lt;p&gt;And this is where I landed. Stuck between an HTTP client that wouldn&apos;t add a CRLF and an API that expected it. It took me far too long to figure this out.&lt;/p&gt;
&lt;h2&gt;Fixing both sides&lt;/h2&gt;
&lt;p&gt;The latest version of Node.js, as I write this, is 23.6.0 and &lt;a href=&quot;https://github.com/nodejs/node/blob/v23.6.0/deps/undici/src/lib/web/fetch/body.js#L157&quot;&gt;it still behaves this way&lt;/a&gt;. However, the code has been updated in undici version 7.1.0 to &lt;a href=&quot;https://github.com/KhafraDev/undici/blob/d48754e879645fe3818aaded1baef86196b0bcf4/lib/web/fetch/body.js#L158&quot;&gt;include the trailing CRLF&lt;/a&gt; and I am sure it will be in a release version of Node.js soon. I&apos;m loathe to call this a fix as there was technically nothing wrong with what they were previously doing, but convention wins here.&lt;/p&gt;
&lt;p&gt;On the other side of things, I made a &lt;a href=&quot;https://github.com/langflow-ai/langflow/pull/5660&quot;&gt;pull request to loosen Langflow&apos;s definition of a valid multipart request&lt;/a&gt;. I&apos;ll have to wait to see how that goes.&lt;/p&gt;
&lt;p&gt;As for my own code, I installed the latest version of undici into the project, imported &lt;code&gt;fetch&lt;/code&gt; and it started working immediately.&lt;/p&gt;
&lt;p&gt;So, if you&apos;re using &lt;code&gt;fetch&lt;/code&gt; in Node.js between version 18.0.0 and 23.6.0 and you&apos;re making requests to a server that expects a multipart request to end in CRLF, you too have felt this pain and I am sorry. Yes, it&apos;s specific, but what else is the web for if not for sharing very specific problems and how you eventually mananaged to fix them?&lt;/p&gt;
</description><pubDate>Tue, 14 Jan 2025 00:00:00 GMT</pubDate><category>javascript</category><category>nodejs</category><category>fetch</category></item><item><title>Clean up HTML Content for Retrieval-Augmented Generation with Readability.js</title><link>https://philna.sh/blog/2025/01/09/html-content-retrieval-augmented-generation-readability-js/</link><guid isPermaLink="true">https://philna.sh/blog/2025/01/09/html-content-retrieval-augmented-generation-readability-js/</guid><description>&lt;p&gt;Scraping web pages is one way to fetch content for your &lt;a href=&quot;https://www.ibm.com/think/topics/retrieval-augmented-generation&quot;&gt;retrieval-augmented generation (RAG)&lt;/a&gt; application. But parsing the content from a web page can be a pain.&lt;/p&gt;
&lt;p&gt;Mozilla&apos;s open-source library &lt;a href=&quot;https://github.com/mozilla/readability&quot;&gt;Readability.js&lt;/a&gt; is a useful tool for extracting just the important parts of a web page. Let&apos;s look at how to use it as part of a data ingestion pipeline for a RAG application.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This post was originally written for DataStax, but didn&apos;t survive a content migration as part of &amp;lt;a href=&quot;https://www.ibm.com/new/announcements/ibm-to-acquire-datastax-helping-clients-bring-the-power-of-unstructured-data-to-enterprise-ai-applications&quot;&amp;gt;IBM&apos;s purchase&amp;lt;/a&amp;gt;. I thought the content was useful, so have republished it here.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Retrieving unstructured data from a web page&lt;/h2&gt;
&lt;p&gt;Web pages are a source of unstructured data that we can use in RAG-based apps. But web pages are often full of content that is irrelevant; things like headers, sidebars, and footers. They contain useful context for someone browsing the site, but detract from the main subject of a page.&lt;/p&gt;
&lt;p&gt;To get the best data for RAG, we need to remove irrelevant content. When you’re working within one site, you can use tools like &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;cheerio&lt;/a&gt; to parse the HTML yourself based on your knowledge of the site&apos;s structure. But if you&apos;re scraping pages across different layouts and designs, you need a good way to return just the relevant content and avoid the rest.&lt;/p&gt;
&lt;h2&gt;Repurposing reader view&lt;/h2&gt;
&lt;p&gt;Most web browsers come with a reader view that strips out everything but the article title and content. Here is the difference between the browser and reader mode when applied to a blog post on my personal site.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Mozilla makes the underlying library for Firefox&apos;s reader mode available as a standalone open-source module: &lt;a href=&quot;https://github.com/mozilla/readability&quot;&gt;Readability.js&lt;/a&gt;. So we can use Readability.js in a data pipeline to strip irrelevant content and return high quality results from scraping a web page.&lt;/p&gt;
&lt;h2&gt;How to scrape data with Node.js and Readability.js&lt;/h2&gt;
&lt;p&gt;Let&apos;s take a look at an example of scraping the article content from my previous blog post on &lt;a href=&quot;/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;&gt;creating vector embeddings in Node.js&lt;/a&gt;. Here&apos;s some JavaScript you can use to retrieve the HTML for the page:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const html = await fetch(
  &quot;https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;
).then((res) =&amp;gt; res.text());
console.log(html);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This includes all the HTML tags as well as the navigation, footer, share links, calls to action and other things you can find on most web sites.&lt;/p&gt;
&lt;p&gt;To improve on this, you could install a module like &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;cheerio&lt;/a&gt; and select only the important parts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install cheerio
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import * as cheerio from &quot;cheerio&quot;;

const html = await fetch(
  &quot;https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;
).then((res) =&amp;gt; res.text());

const $ = cheerio.load(html);

console.log($(&quot;h1&quot;).text(), &quot;\n&quot;);
console.log($(&quot;section#blog-content &amp;gt; div:first-child&quot;).text());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this code you get the title and text of the article. As I said earlier, this is great if you know the structure of the HTML, but that won&apos;t always be the case.&lt;/p&gt;
&lt;p&gt;Instead, install &lt;a href=&quot;https://github.com/mozilla/readability&quot;&gt;Readability.js&lt;/a&gt; and &lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;jsdom&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @mozilla/readability jsdom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Readability.js normally runs in a browser environment and uses the live document rather than a string of HTML, so we need to include jsdom to provide that in Node.js. Now we can turn the HTML we already loaded into a document and pass it to Readability.js to parse out the content.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Readability } from &quot;@mozilla/readability&quot;;
import { JSDOM } from &quot;jsdom&quot;;

const url =
  &quot;https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;;
const html = await fetch(url).then((res) =&amp;gt; res.text());

const doc = new JSDOM(html, { url });
const reader = new Readability(doc.window.document);
const article = reader.parse();

console.log(article);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you inspect the article, you can see that it has parsed a number of things from the HTML.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There&apos;s the title, author, excerpt, publish time, and both the content and textContent. The textContent property is the plain text content of the article, ready for you to &lt;a href=&quot;/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/&quot;&gt;split into chunks&lt;/a&gt;, &lt;a href=&quot;/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;&gt;create vector embeddings&lt;/a&gt;, and ingest into a vector database. The content property is the original HTML, including links and images. This could be useful if you want to extract links or process the images somehow.&lt;/p&gt;
&lt;p&gt;You might also want to see whether the document is likely to return good results. Reader view works well on articles, but is less useful for other types of content. You can do a quick check to see if the HTML is suitable for processing with Readability.js with the function &lt;code&gt;isProbablyReaderable&lt;/code&gt;. If this function returns &lt;code&gt;false&lt;/code&gt; you may want to parse the HTML in a different way, or even inspect the contents at that URL to see whether it has useful content for you.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const doc = new JSDOM(html, { url });
const reader = new Readability(doc.window.document);

if (isProbablyReaderable(doc.window.document)) {
  const article = reader.parse();
  console.log(article);
} else {
  // do something else
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the page fails this check, you might want to flag the URL to see whether it does include useful information for your RAG application, or whether it should be excluded.&lt;/p&gt;
&lt;h2&gt;Using Readability with LangChain.js&lt;/h2&gt;
&lt;p&gt;If you&apos;re using &lt;a href=&quot;https://docs.langchain.com/oss/javascript/langchain/overview&quot;&gt;LangChain.js&lt;/a&gt; for your application, you can also use Readability.js to return the content from an HTML page. It fits nicely into your data ingestion pipelines, working with other LangChain components, like &lt;a href=&quot;/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/&quot;&gt;text chunkers&lt;/a&gt; and vector stores.&lt;/p&gt;
&lt;p&gt;The following example uses LangChain.js to load the same page as above, return the relevant content from the page using the &lt;a href=&quot;https://docs.langchain.com/oss/javascript/integrations/document_transformers/mozilla_readability&quot;&gt;&lt;code&gt;MozillaReadabilityTransformer&lt;/code&gt;&lt;/a&gt;, split the text into chunks using the &lt;a href=&quot;https://docs.langchain.com/oss/javascript/integrations/splitters/recursive_text_splitter&quot;&gt;&lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;&lt;/a&gt;, create vector embeddings with OpenAI, and store the data in Astra DB.&lt;/p&gt;
&lt;p&gt;You&apos;ll need to install the following dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @langchain/core @langchain/community @langchain/openai @datastax/astra-db-ts @mozilla/readability jsdom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run the example, you will need to create an Astra DB database and store the database&apos;s endpoint and application token in your environment as &lt;code&gt;ASTRA_DB_APPLICATION_TOKEN&lt;/code&gt; and &lt;code&gt;ASTRA_DB_API_ENDPOINT&lt;/code&gt;. You will also need an OpenAI API key stored in your environment as &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Import the dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTMLWebBaseLoader } from &quot;@langchain/community/document_loaders/web/html&quot;;
import { MozillaReadabilityTransformer } from &quot;@langchain/community/document_transformers/mozilla_readability&quot;;
import { RecursiveCharacterTextSplitter } from &quot;@langchain/textsplitters&quot;;
import { OpenAIEmbeddings } from &quot;@langchain/openai&quot;;
import { AstraDBVectorStore } from &quot;@langchain/community/vectorstores/astradb&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use the &lt;code&gt;HTMLWebBaseLoader&lt;/code&gt; to load the raw HTML from the URL we provide. The HTML is then passed through the &lt;code&gt;MozillaReadabilityTransformer&lt;/code&gt; to extract the text, which is then split into chunks by the &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;. Finally, we create an embedding provider and an Astra DB vector store that will be used to turn the text chunks into vector embeddings and store them in the vector database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const loader = new HTMLWebBaseLoader(
  &quot;https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/&quot;
);
const transformer = new MozillaReadabilityTransformer();
const splitter = new RecursiveCharacterTextSplitter({
  maxCharacterCount: 1000,
  chunkOverlap: 200,
});
const embeddings = new OpenAIEmbeddings({
  model: &quot;text-embedding-3-small&quot;,
});
const vectorStore = new AstraDBVectorStore(embeddings, {
  token: process.env.ASTRA_DB_APPLICATION_TOKEN,
  endpoint: process.env.ASTRA_DB_API_ENDPOINT,
  collection: &quot;content&quot;,
  collectionOptions: {
    vector: {
      dimension: 1536,
      metric: &quot;cosine&quot;,
    },
  },
});
await vectorStore.initialize();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The initialisation of all the components makes up most of the work. Once everything is set up, you can load, transform, split, embed and store the documents like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const docs = await loader.load();
const sequence = transformer.pipe(splitter);
const vectorizedDocs = await sequence.invoke(docs);
await vectorStore.addDocuments(vectorizedDocs);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;More accurate data from web scraping with Readability.js&lt;/h2&gt;
&lt;p&gt;Readability.js is a battle-tested library powering Firefox&apos;s reader mode that we can use to scrape only relevant data from web pages. This cleans up web content and makes it much more useful for RAG.&lt;/p&gt;
&lt;p&gt;As we&apos;ve seen, you can do this directly with the library or using LangChain.js and the &lt;code&gt;MozillaReadabilityTransformer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Getting data from a web page is only the first step in your ingestion pipeline. From here you&apos;ll need to split your text into chunks, create vector embeddings, and store everything in a vector database. Then you&apos;ll be ready to build your RAG-powered application.&lt;/p&gt;
</description><pubDate>Thu, 09 Jan 2025 00:00:00 GMT</pubDate><category>javascript</category><category>nodejs</category><category>genai</category></item><item><title>Shallow clones versus structured clones</title><link>https://philna.sh/blog/2024/12/30/shallow-clones-versus-structured-clones/</link><guid isPermaLink="true">https://philna.sh/blog/2024/12/30/shallow-clones-versus-structured-clones/</guid><description>&lt;p&gt;Have you ever had one of those times when you think you&apos;re doing everything right, yet still you get an unexpected bug in your application? Particularly when it is state-related and you thought you did everything you could to isolate the state by making copies instead of mutating it in place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Especially&lt;/em&gt; when you are, say, &lt;a href=&quot;https://www.datastax.com/blog/building-unreel-ai-movie-quiz&quot;&gt;building a game&lt;/a&gt; that copies a blank initial state when you create a new room and, no matter what you do, you still find that &lt;em&gt;every player is in every room&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you find yourself in this sort of situation, like I might have recently, then it&apos;s almost certain that you have nested state, you are only making a shallow clone, and you should be using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone&quot;&gt;&lt;code&gt;structuredClone&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Shallow copies of nested states&lt;/h2&gt;
&lt;p&gt;Here&apos;s a simple version of the issue I described above. We have a default state and when we generate a new room that state is cloned to the room. The room has a function to add a player to its state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const defaultState = {
  roomName: &quot;&quot;,
  players: []
}

class Room {
  constructor(name) {
    this.state = { ...defaultState, roomName: name }
  }

  addPlayer(playerName) {
    this.state.players.push(playerName);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can create a new room and add a player to it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const room = new Room(&quot;room1&quot;);
room.addPlayer(&quot;Phil&quot;);
console.log(room.state);
// { roomName: &quot;room1&quot;, players: [&quot;Phil&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But if you try to create a second room, you&apos;ll find the player is already in there.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const room2 = new Room(&quot;room2&quot;);
console.log(room2.state);
// { roomName: &quot;room2&quot;, players: [&quot;Phil&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turns out the player even entered the default state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(defaultState);
// { roomName: &quot;&quot;, players: [&quot;Phil&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issue is the clone of the default state that was made in the constructor. It uses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax&quot;&gt;object spread syntax&lt;/a&gt; (though it could have been using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign&quot;&gt;&lt;code&gt;Object.assign&lt;/code&gt;&lt;/a&gt;) to make a &lt;em&gt;shallow clone&lt;/em&gt; of the default state object, but a shallow clone is only useful for cloning primitive values in the object. If you have values like arrays or objects, they aren&apos;t cloned, the reference to the original object is cloned.&lt;/p&gt;
&lt;p&gt;You can see this because the &lt;code&gt;players&lt;/code&gt; array in the above example is equal across the default state and the two rooms.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defaultState.players === room.state.players;
// true
defaultState.players === room2.state.players;
// true
room.state.players === room2.state.players;
// true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since all these references point to the same object, whenever you make an update to any room&apos;s players, all rooms and the default state will reflect that.&lt;/p&gt;
&lt;h2&gt;How to make a deep clone&lt;/h2&gt;
&lt;p&gt;There have been many ways in JavaScript over the years to make deep clones of objects, examples include &lt;a href=&quot;https://lodash.com/docs/#cloneDeep&quot;&gt;Lodash&apos;s &lt;code&gt;cloneDeep&lt;/code&gt;&lt;/a&gt; and using &lt;code&gt;JSON&lt;/code&gt; to &lt;code&gt;stringify&lt;/code&gt; and then &lt;code&gt;parse&lt;/code&gt; an object. However it turned out that the web platform already had an underlying algorithm to perform deep clones through APIs like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage&quot;&gt;&lt;code&gt;postMessage&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2015 &lt;a href=&quot;https://lists.w3.org/Archives/Public/public-webapps/2015AprJun/0251.html&quot;&gt;it was suggested on a W3C mailing list&lt;/a&gt; that the algorithm was exposed publicly, though it took until late 2021 when Deno, Node.js and Firefox released support for &lt;code&gt;structuredClone&lt;/code&gt;, followed in 2022 by the other browsers.&lt;/p&gt;
&lt;p&gt;If you want to make a deep clone of an object in JavaScript you should use &lt;code&gt;structuredClone&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Using structuredClone&lt;/h3&gt;
&lt;p&gt;Let&apos;s see the function in action. If we update the &lt;code&gt;Room&lt;/code&gt; class from the example above to use &lt;code&gt;structuredClone&lt;/code&gt;, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Room {
  constructor(name) {
    this.state = structuredClone(defaultState);
    this.state.roomName = name;
  }

  addPlayer(playerName) {
    this.state.players.push(playerName);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating one room acts as it did before:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const room = new Room(&quot;room1&quot;);
room.addPlayer(&quot;Phil&quot;);
console.log(room.state);
// { roomName: &quot;room1&quot;, players: [&quot;Phil&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But creating a second room now works as expected, the players are no longer shared.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const room2 = new Room(&quot;room2&quot;);
console.log(room2.state);
// { roomName: &apos;room2&apos;, players: [] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the players arrays are no longer equal references, but completely different objects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;defaultState.players === room.state.players;
// false
defaultState.players === room2.state.players;
// false
room.state.players === room2.state.players;
// false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is worth reading how the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm&quot;&gt;structured clone algorithm&lt;/a&gt; works and what doesn&apos;t work. For example, you cannot deep clone &lt;code&gt;Function&lt;/code&gt; objects or DOM nodes, but circular references are handled with no problem.&lt;/p&gt;
&lt;h2&gt;What a state&lt;/h2&gt;
&lt;p&gt;If you find yourself in a pickle when trying to clone your state, it may be because you are only making a shallow clone. If you have a simple state that is made up of primitives, then it is fine to use a shallow clone. Once you introduce nested objects that&apos;s when you need to consider using &lt;code&gt;structuredClone&lt;/code&gt; to create a deep clone and avoid just copying references.&lt;/p&gt;
&lt;p&gt;If you do find yourself facing this issue, I hope it takes you less time than it took me to realise what was going on.&lt;/p&gt;
</description><pubDate>Mon, 30 Dec 2024 00:00:00 GMT</pubDate><category>javascript</category></item><item><title>How to Create Vector Embeddings in Node.js</title><link>https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/</link><guid isPermaLink="true">https://philna.sh/blog/2024/09/25/how-to-create-vector-embeddings-in-node-js/</guid><description>&lt;p&gt;When you’re building a &lt;a href=&quot;https://www.ibm.com/think/topics/retrieval-augmented-generation&quot;&gt;retrieval-augmented generation (RAG)&lt;/a&gt; app, job number one is preparing your data. You’ll need to take your unstructured data and &lt;a href=&quot;/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/&quot;&gt;split it up into chunks&lt;/a&gt;, turn those chunks into &lt;a href=&quot;https://www.ibm.com/think/topics/vector-embedding&quot;&gt;vector embeddings&lt;/a&gt;, and finally, store the embeddings in a &lt;a href=&quot;https://www.ibm.com/think/topics/vector-database&quot;&gt;vector database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are many ways that you can create vector embeddings in JavaScript. In this post, we’ll investigate four ways to generate vector embeddings in Node.js: locally, via API, via a framework, and with Astra DB&apos;s Vectorize.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This post was originally written for DataStax, but didn&apos;t survive a content migration as part of &amp;lt;a href=&quot;https://www.ibm.com/new/announcements/ibm-to-acquire-datastax-helping-clients-bring-the-power-of-unstructured-data-to-enterprise-ai-applications&quot;&amp;gt;IBM&apos;s purchase&amp;lt;/a&amp;gt;. I thought the content was useful, so have republished it here.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Local vector embeddings&lt;/h2&gt;
&lt;p&gt;There are lots of open-source models available on &lt;a href=&quot;https://huggingface.co/&quot;&gt;HuggingFace&lt;/a&gt; that can be used to create vector embeddings. &lt;a href=&quot;https://huggingface.co/docs/transformers.js/en/index&quot;&gt;Transformers.js&lt;/a&gt; is a module that lets you use machine learning models in JavaScript, both in the browser and Node.js. It uses the &lt;a href=&quot;https://onnxruntime.ai/&quot;&gt;ONNX runtime&lt;/a&gt; to achieve this; it works with models that have published ONNX weights, of which there are plenty. Some of those models we can use to create vector embeddings.&lt;/p&gt;
&lt;p&gt;You can install the module with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @huggingface/transformers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/docs/transformers.js/index#tasks&quot;&gt;The package can actually perform many tasks&lt;/a&gt;, but &lt;a href=&quot;https://huggingface.co/docs/transformers.js/api/pipelines#module_pipelines.FeatureExtractionPipeline&quot;&gt;feature extraction&lt;/a&gt; is what you want for generating vector embeddings.&lt;/p&gt;
&lt;p&gt;A popular, local model for vector embedding is &lt;a href=&quot;https://huggingface.co/Xenova/all-MiniLM-L6-v2&quot;&gt;all-MiniLM-L6-v2&lt;/a&gt;. It’s trained as a good all-rounder and produces a 384-dimension vector from a chunk of text.&lt;/p&gt;
&lt;p&gt;To use it, import the &lt;code&gt;pipeline&lt;/code&gt; function from Transformers.js and create an extractor that will perform &quot;feature-extraction&quot; using your provided model. You can then pass a chunk of text to the extractor and it will return a tensor object which you can turn into a plain JavaScript array of numbers.&lt;/p&gt;
&lt;p&gt;All in all, it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { pipeline } from &quot;@huggingface/transformers&quot;;

const extractor = await pipeline(
  &quot;feature-extraction&quot;,
  &quot;Xenova/all-MiniLM-L6-v2&quot;
);

const response = await extractor(
  [
    &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
  ],
  { pooling: &quot;mean&quot;, normalize: true }
);

console.log(Array.from(response.data));
// =&amp;gt; [-0.004044221248477697,  0.026746056973934174,   0.0071970801800489426, ... ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can actually embed multiple texts at a time if you pass an array to the extractor. Then you can call &lt;code&gt;tolist&lt;/code&gt; on the response and that will return you a list of arrays as your vectors.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const response = await extractor(
  [
    &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
    &quot;A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.&quot;,
    &quot;A robot must protect its own existence as long as such protection does not conflict with the First or Second Law.&quot;,
  ],
  { pooling: &quot;mean&quot;, normalize: true }
);

console.log(response.tolist());
// [
//   [ -0.006129210349172354,  0.016346964985132217,   0.009711502119898796, ...],
//   [-0.053930871188640594,  -0.002175076398998499,   0.032391052693128586, ...],
//   [-0.05358131229877472,  0.021030642092227936, 0.0010665050940588117, ...]
// ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are &lt;a href=&quot;https://huggingface.co/models?pipeline_tag=feature-extraction&amp;amp;library=transformers.js&quot;&gt;many models you can use to create vector embeddings from text&lt;/a&gt;, and, because you’re running locally, you can try them out to see which works best for your data. You should pay attention to the length of text that these models can handle. For example, the all-MiniLM-L6-v2 model does not provide good results for more than 128 &lt;a href=&quot;https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them&quot;&gt;tokens&lt;/a&gt; and can handle a maximum of 256 tokens, so it’s useful for sentences or small paragraphs. If you have a bigger source of text data than that, you’ll need to &lt;a href=&quot;/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/&quot;&gt;split your data into appropriately sized chunks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Local embedding models like this are useful if you’re experimenting on your own machine, or have the &lt;a href=&quot;https://onnxruntime.ai/docs/get-started/with-javascript/node.html&quot;&gt;right hardware to run them efficiently when deployed&lt;/a&gt;. It&apos;s an easy way to get comfortable with different models and get a feel for how things work without having to sign up to a bunch of different API services.&lt;/p&gt;
&lt;p&gt;Having said that, there are a lot of useful vector embedding models available as an API, so let&apos;s take a look at them next.&lt;/p&gt;
&lt;h2&gt;APIs&lt;/h2&gt;
&lt;p&gt;There are an abundance of services that provide embedding models as APIs. These include LLM providers, like &lt;a href=&quot;https://platform.openai.com/docs/guides/embeddings&quot;&gt;OpenAI&lt;/a&gt;, &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/embeddings&quot;&gt;Google&lt;/a&gt; or &lt;a href=&quot;https://docs.cohere.com/docs/cohere-embed&quot;&gt;Cohere&lt;/a&gt;, as well as specialist providers like &lt;a href=&quot;https://www.voyageai.com/&quot;&gt;Voyage AI&lt;/a&gt; or &lt;a href=&quot;https://jina.ai/embeddings/&quot;&gt;Jina&lt;/a&gt;. Most providers have general purpose embedding models, but some provide models trained for specific datasets, like &lt;a href=&quot;https://docs.voyageai.com/docs/embeddings&quot;&gt;Voyage AI&apos;s finance, law and code optimised models&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These API providers provide HTTP APIs, often with an npm package to make it easy to call them. You’ll typically need an API key from the service and you can then generate embeddings by sending your text to the API.&lt;/p&gt;
&lt;p&gt;For example, you can &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/embeddings&quot;&gt;use Google&apos;s text embedding models through the Gemini API&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { GoogleGenAI } from &quot;@google/genai&quot;;

const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
const text =
  &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;;

const response = await ai.models.embedContent({
  model: &quot;gemini-embedding-001&quot;,
  contents: text,
});

console.log(response.embeddings[0].values);
// =&amp;gt; [ -0.0049246787, 0.031826325, -0.0075687882, ... ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each API is different though, so while making a request to create embeddings is normally fairly straightforward, you’ll likely have to learn a new method for each API you want to call—unless of course, you try one of the available frameworks that are intended to simplify this.&lt;/p&gt;
&lt;h2&gt;Frameworks&lt;/h2&gt;
&lt;p&gt;There are many projects out there, like &lt;a href=&quot;https://js.langchain.com/docs/introduction/&quot;&gt;LangChain&lt;/a&gt; or &lt;a href=&quot;https://ts.llamaindex.ai/&quot;&gt;LlamaIndex&lt;/a&gt;, that create abstractions over the various parts of the GenAI toolchain, including embeddings.&lt;/p&gt;
&lt;p&gt;Both LangChain and LlamaIndex enable you to generate embeddings via APIs or local models, all with the same interface. For example, here’s how you can &lt;a href=&quot;https://js.langchain.com/docs/integrations/text_embedding/google_generativeai/&quot;&gt;create the same embedding as above using the Gemini API and LangChain&lt;/a&gt; together:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { GoogleGenerativeAIEmbeddings } from &quot;@langchain/google-genai&quot;;

const embeddings = new GoogleGenerativeAIEmbeddings({
  apiKey: process.env.API_KEY,
  model: &quot;gemini-embedding-001&quot;,
});
const text =
  &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;;

const embedding = await embeddings.embedQuery(text);
console.log(embedding);
// =&amp;gt; [-0.0049246787, 0.031826325, -0.0075687882, ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To compare, this is what it looks like to use the &lt;a href=&quot;https://js.langchain.com/docs/integrations/text_embedding/openai/&quot;&gt;OpenAI embeddings model through LangChain&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { OpenAIEmbeddings } from &quot;@langchain/openai&quot;;

const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.API_KEY,
  model: &quot;text-embedding-3-large&quot;,
});
const text =
  &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;;

const embedding = await embeddings.embedQuery(text);
console.log(embedding);
// =&amp;gt; [0.009445431, -0.0073068426, -0.00814802, ...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aside from changing the name of the import and sometimes the options, the embedding models all have a consistent interface to make it easier to swap them out.&lt;/p&gt;
&lt;p&gt;If you’re using LangChain to create your entire pipeline, these embedding interfaces work very well alongside the vector database interfaces. You can provide an embedding model to the database integration and LangChain handles generating the embeddings as you insert documents or perform vector searches. For example, here is how to embed some documents using Google&apos;s embeddings and store them in &lt;a href=&quot;https://js.langchain.com/docs/integrations/vectorstores/astradb/&quot;&gt;Astra DB via LangChain&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { GoogleGenerativeAIEmbeddings } from &quot;@langchain/google-genai&quot;;
import { AstraDBVectorStore } from &quot;@langchain/community/vectorstores/astradb&quot;;

const embeddings = new GoogleGenerativeAIEmbeddings({
  apiKey: process.env.API_KEY,
  model: &quot;gemini-embedding-001&quot;,
});

const vectorStore = await AstraDBVectorStore.fromDocuments(
  documents, // a list of document objects to put in the store
  embeddings, // the embeddings model
  astraConfig // config to connect to Astra DB
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you provide the embeddings model to the database object, you can then use it to perform vector searches too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const results = vectorStore.similaritySearch(
  &quot;Are robots allowed to protect themselves?&quot;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LlamaIndex allows for similar creation of embedding models and vector stores that use them. Check out &lt;a href=&quot;https://ts.llamaindex.ai/docs/llamaindex/tutorials/rag&quot;&gt;the LlamaIndex documentation on RAG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a bonus, the lists of models that &lt;a href=&quot;https://js.langchain.com/docs/integrations/text_embedding/&quot;&gt;LangChain&lt;/a&gt; and &lt;a href=&quot;https://ts.llamaindex.ai/docs/llamaindex/modules/embeddings&quot;&gt;LlamaIndex&lt;/a&gt; integrate are good examples of popular embedding models.&lt;/p&gt;
&lt;h2&gt;Directly in the database&lt;/h2&gt;
&lt;p&gt;So far, the methods above mostly involve creating a vector embedding independently of storing the embedding in a vector database. When you want to store those vectors in a vector database like Astra DB, it looks a bit like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { DataAPIClient } from &quot;@datastax/astra-db-ts&quot;;
const client = new DataAPIClient(process.env.ASTRA_DB_APPLICATION_TOKEN);
const db = client.db(process.env.ASTRA_DB_API_ENDPOINT);
const collection = db.collection(process.env.ASTRA_DB_COLLECTION);

await collection.insertOne({
  text: &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
  $vector: [0.04574034, 0.038084425, -0.00916391, ...]
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This assumes you have already created a vector enabled collection with the correct number of dimensions for the model you are using.&lt;/p&gt;
&lt;p&gt;You can also search against the documents in your collection using a vector like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cursor = collection.find({}, {
  sort: { $vector: [0.04574034, 0.038084425, -0.00916391, ...] },
  limit: 5,
});
const results = await cursor.toArray();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, you have to create your vectors first, and then store or search against the database with them. Even in the case of the frameworks, that process happens, but it’s just abstracted away.&lt;/p&gt;
&lt;p&gt;With Astra DB, you can have the database generate the embeddings for you as you’re inserting documents into a collection or as you perform a vector search against a collection.&lt;/p&gt;
&lt;p&gt;This is called &lt;a href=&quot;https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html&quot;&gt;Astra DB vectorize&lt;/a&gt;; here&apos;s how it works.&lt;/p&gt;
&lt;p&gt;First, set up an embedding provider integration. There is a built-in integration offering the &lt;a href=&quot;https://build.nvidia.com/nvidia/embed-qa-4&quot;&gt;NVIDIA NV-Embed-QA model&lt;/a&gt;, or you can choose one of the other providers and configure them with your own API key.&lt;/p&gt;
&lt;p&gt;Then when you set up a collection, you can choose which embedding provider you want to use and set the correct number of dimensions.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now, when you add a document to this collection, you can add the content using the special key &lt;code&gt;$vectorize&lt;/code&gt; and a vector embedding will be created.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await collection.insertOne({
  $vectorize:
    &quot;A robot may not injure a human being or, through inaction, allow a human being to come to harm.&quot;,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you want to perform a vector search against this collection, you can sort by the special &lt;code&gt;$vectorize&lt;/code&gt; field and again, Astra DB will handle creating vector embeddings and then performing the search.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const cursor = collection.find(
  {},
  {
    sort: { $vectorize: &quot;Are robots allowed to protect themselve?&quot; },
    limit: 5,
  }
);
const results = await cursor.toArray();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&apos;s robust, as Astra DB handles the interaction with the embedding provider&lt;/li&gt;
&lt;li&gt;It can be quicker than making two separate API calls to create embeddings and then store them&lt;/li&gt;
&lt;li&gt;It&apos;s less code for you to write&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Choose the method that works best for your application&lt;/h2&gt;
&lt;p&gt;There are many models, providers, and methods you can use to turn text into vector embeddings. Creating vector embeddings from your content is a vital part of the RAG pipeline and it does require some experimentation to get it right for your data.&lt;/p&gt;
&lt;p&gt;You have the choice to host your own models, call on APIs, use a framework, or let Astra DB handle creating vector embeddings for you. And, if you want to avoid code altogether, you could choose to use &lt;a href=&quot;https://docs.langflow.org/chat-with-rag&quot;&gt;Langflow&apos;s drag-and-drop interface to create your RAG pipeline&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 25 Sep 2024 00:00:00 GMT</pubDate><category>javascript</category><category>nodejs</category><category>genai</category><category>vector embeddings</category></item><item><title>How to Chunk Text in JavaScript for Your RAG Application</title><link>https://philna.sh/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/</link><guid isPermaLink="true">https://philna.sh/blog/2024/09/18/how-to-chunk-text-in-javascript-for-rag-applications/</guid><description>&lt;p&gt;&lt;a href=&quot;https://www.ibm.com/think/topics/retrieval-augmented-generation&quot;&gt;Retrieval-augmented generation (RAG)&lt;/a&gt; applications begin with data, so getting your data in the right shape to work well with &lt;a href=&quot;https://www.ibm.com/think/topics/vector-database&quot;&gt;vector databases&lt;/a&gt; and &lt;a href=&quot;https://www.ibm.com/think/topics/large-language-models&quot;&gt;large language models (LLMs)&lt;/a&gt; is the first challenge you’re likely to face when you get started building. In this post, we&apos;ll discuss the different ways to work with text data in JavaScript, exploring how to split it up into chunks and prepare it for use in a RAG app.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This post was originally written for DataStax, but didn&apos;t survive a content migration as part of &amp;lt;a href=&quot;https://www.ibm.com/new/announcements/ibm-to-acquire-datastax-helping-clients-bring-the-power-of-unstructured-data-to-enterprise-ai-applications&quot;&amp;gt;IBM&apos;s purchase&amp;lt;/a&amp;gt;. I thought the content was useful, so have republished it here.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Why chunking is important&lt;/h2&gt;
&lt;p&gt;Often you will have swaths of unstructured text content that need to be broken down into smaller chunks of data. These chunks of text are turned into vector embeddings and stored in a vector database like &lt;a href=&quot;https://docs.datastax.com/en/astra-db-serverless/index.html&quot;&gt;Astra DB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Compared to a whole document, smaller chunks of text capture fewer topics or ideas, which means that their embeddings will contain more focused meaning. This makes each chunk easier to match against incoming user queries and makes for more accurate retrieval. When you improve your retrieval, you can feed fewer, but more relevant, tokens to an LLM and create a more accurate and useful RAG system.&lt;/p&gt;
&lt;p&gt;If you want to read more on the theory behind text chunking check out this post from Unstructured on &lt;a href=&quot;https://unstructured.io/blog/chunking-for-rag-best-practices&quot;&gt;best practices for chunking&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Chunking in JavaScript&lt;/h2&gt;
&lt;p&gt;Let&apos;s move beyond theory and take a look at the options in JavaScript to chunk your data. The libraries we&apos;re going to look at are: &lt;a href=&quot;https://github.com/golbin/llm-chunk&quot;&gt;llm-chunk&lt;/a&gt;, &lt;a href=&quot;https://js.langchain.com/v0.1/docs/modules/data_connection/document_transformers/&quot;&gt;LangChain&lt;/a&gt;, &lt;a href=&quot;https://ts.llamaindex.ai/modules/node_parser&quot;&gt;LlamaIndex&lt;/a&gt;, and &lt;a href=&quot;https://github.com/jparkerweb/semantic-chunking&quot;&gt;semantic-chunking&lt;/a&gt;. You can &lt;a href=&quot;https://chunkers.vercel.app/&quot;&gt;experiment with these chunking libraries using this app&lt;/a&gt; I put together.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We&apos;ll also take a look at using the &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/overview&quot;&gt;Unstructured API&lt;/a&gt; for more complex use cases. Let&apos;s get started with the simplest of these modules.&lt;/p&gt;
&lt;h2&gt;llm-chunk&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/golbin/llm-chunk&quot;&gt;llm-chunk&lt;/a&gt; describes itself as a &quot;super simple and easy-to-use text splitter&quot; and it is! You can install it using npm with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install llm-chunk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It consists of one function, &lt;code&gt;chunk&lt;/code&gt;, and it takes just a few options. You can choose maximum and minimum sizes for the chunks it produces, pick the size of the overlap between chunks, and choose a strategy for splitting the text, either by sentence or paragraph.&lt;/p&gt;
&lt;p&gt;By default, it will split text up by paragraphs with a maximum length of 1,000 characters, and no minimum length or overlap.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { chunk } from &quot;llm-chunk&quot;;
const text = loadText(); // get some text to split
const chunks = chunk(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can choose to split the text by sentences, alter the maximum and minimum number of characters by chunk, or by how many characters each chunk should overlap.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const chunks = chunk(text, { minLength: 128, maxLength: 1024, overlap: 128 });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more complex use cases, you can opt to parse a set of delimiters. These get turned into a regular expression and used as the basis for splitting up the text instead of just by paragraph or sentence. llm-chunk is simple, fast, and a great option when you are starting your RAG journey.&lt;/p&gt;
&lt;h2&gt;LangChain&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://js.langchain.com/&quot;&gt;LangChain&lt;/a&gt; is much more than a text splitter. It is a library that helps you load data, split it up, embed it, store that data in and retrieve it from vector databases, feed it as a prompt to LLMs, and more. There’s a lot to explore with &lt;a href=&quot;https://js.langchain.com/&quot;&gt;LangChain&lt;/a&gt;, but we’re going to concentrate on text splitting.&lt;/p&gt;
&lt;p&gt;You can install the LangChain text splitters with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @langchain/textsplitters
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using the main &lt;a href=&quot;https://www.npmjs.com/package/langchain&quot;&gt;langchain module&lt;/a&gt; then &lt;code&gt;@langchain/textsplitters&lt;/code&gt; is included as a dependency.&lt;/p&gt;
&lt;p&gt;LangChain includes three main splitter classes, the &lt;code&gt;CharacterTextSplitter&lt;/code&gt;, &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;, and &lt;code&gt;TokenTextSplitter&lt;/code&gt;. Let&apos;s take a look at what each of them do.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;CharacterTextSplitter&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is the simplest of the splitters provided by LangChain. It splits up a document by a character, then merges segments back together until they reach the desired chunk size, overlapping the chunks by the desired number of characters.&lt;/p&gt;
&lt;p&gt;The default character for splitting up text is &quot;\n\n&quot;. This means it aims to initially split the text by paragraphs, though you can also provide the character you want to split by too. Here&apos;s how you would use this LangChain splitter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { CharacterTextSplitter } from &quot;@langchain/textsplitters&quot;;
const text = loadText(); // get some text to split
const splitter = new CharacterTextSplitter({
  chunkSize: 1024,
  chunkOverlap: 128,
});
const output = await splitter.splitText(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also output &lt;a href=&quot;https://js.langchain.com/v0.2/docs/concepts/#document&quot;&gt;LangChain Documents&lt;/a&gt; which is useful if you are using the rest of LangChain to create a data pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const output = await splitter.createDocuments([text]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;CharacterTextSplitter&lt;/code&gt; is naive, and doesn&apos;t take into account much of the structure of a piece of text. The &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; goes beyond by using a list of separators to progressively break text down until it creates chunks that fit the size you want. By default it splits text first by paragraphs, then sentences, then words.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { RecursiveCharacterTextSplitter } from &quot;@langchain/textsplitters&quot;;
const text = loadText(); // get some text to split
const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1024,
  chunkOverlap: 128,
});
const output = splitter.splitText(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can provide different characters for the &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; to split the text on so it can be used to split up other types of text. There are two classes available that make it easy to split up Markdown or Latex, the &lt;code&gt;MarkdownTextSplitter&lt;/code&gt; and &lt;code&gt;LatexTextSplitter&lt;/code&gt; respectively.&lt;/p&gt;
&lt;p&gt;But this can also be used to &lt;a href=&quot;https://js.langchain.com/v0.1/docs/modules/data_connection/document_transformers/code_splitter/&quot;&gt;split up code&lt;/a&gt; for a number of different languages. For example, if you wanted to split up a JavaScript file, you could do this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const splitter = RecursiveCharacterTextSplitter.fromLanguage(&quot;js&quot;, {
  chunkSize: 300,
  chunkOverlap: 0,
});
const jsOutput = await splitter.splitText(jsCode);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;RecursiveCharacterTextSplitter&lt;/code&gt; is a versatile text splitter and is likely a good first stop when you&apos;re building up a pipeline to ingest data for your RAG application.&lt;/p&gt;
&lt;h3&gt;TokenTextSplitter&lt;/h3&gt;
&lt;p&gt;Taking a different approach, the &lt;code&gt;TokenTextSplitter&lt;/code&gt; turns the text first into &lt;a href=&quot;https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them&quot;&gt;tokens&lt;/a&gt; using &lt;a href=&quot;https://github.com/dqbd/tiktoken&quot;&gt;js-tiktoken&lt;/a&gt;, splits the tokens into chunks and then converts the tokens back into text.&lt;/p&gt;
&lt;p&gt;Tokens are the way that LLMs consume content, a token can be a whole word or just a part of it. &lt;a href=&quot;https://platform.openai.com/tokenizer&quot;&gt;OpenAI has a good representation of how their models break text into tokens&lt;/a&gt;. You can use the &lt;code&gt;TokenTextSplitter&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { TokenTextSplitter } from &quot;@langchain/textsplitters&quot;;
const text = loadText(); // get some text to split
const splitter = new TokenTextSplitter({
  chunkSize: 1024,
  chunkOverlap: 128,
});
const output = splitter.splitText(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;LlamaIndex&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ts.llamaindex.ai/&quot;&gt;LlamaIndex&lt;/a&gt; is also responsible for much more than just text splitting. We&apos;re going to hone in on the splitting capabilities though, which you can use outside of LlamaIndex too.&lt;/p&gt;
&lt;p&gt;LlamaIndex considers chunks of a document as Nodes and the rest of the library works with Nodes. There are three available processors: &lt;code&gt;SentenceSplitter&lt;/code&gt;, &lt;code&gt;MarkdownNodeParser&lt;/code&gt;. and &lt;code&gt;SentenceWindowNodeParser&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can install the entire LlamaIndex suite with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install llamaindex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you just want the text parsers, you can install just the LlamaIndex core:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @llamaindex/core
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;SentenceSplitter&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;SentenceSplitter&lt;/code&gt; is the simplest of the LlamaIndex splitters. It splits the text into sentences and then combines them into a string that is smaller than the provided &lt;code&gt;chunkSize&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;LlamaIndex does this differently from the previous splitters; it measures the size of a chunk in tokens rather than characters. There are approximately four characters to a token and a good default is 1024 characters with an overlap of 128, so you should aim for chunks of 256 tokens and an overlap of 32 tokens.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;SentenceSplitter&lt;/code&gt; returns an array of chunks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { SentenceSplitter } from &quot;@llamaindex/core/node-parser&quot;;
const text = loadText(); // get some text to split
const splitter = new SentenceSplitter({
  chunkSize: 256,
  chunkOverlap: 32,
});
const output = splitter.splitText(text);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;MarkdownNodeParser&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If you have Markdown to split up then the &lt;code&gt;MarkdownNodeParser&lt;/code&gt; will split it up into logical sections based on the headers in the document.&lt;/p&gt;
&lt;p&gt;This splitter doesn&apos;t let you set a &lt;code&gt;chunkSize&lt;/code&gt; or &lt;code&gt;overlap&lt;/code&gt;, so you do give up that level of control. It also works over LlamaIndex Documents or Nodes. In this example we turn our text into a Document first, then get the Nodes from the Documents.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { MarkdownNodeParser } from &quot;@llamaindex/core/node-parser&quot;;
import { Document } from &quot;@llamaindex/core/schema&quot;;
const text = loadText(); // get some text to split
const splitter = new MarkdownNodeParser();
const nodes = splitter.getNodesFromDocuments([new Document({ text })]);
const output = nodes.map((node) =&amp;gt; node.text);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;SentenceWindowNodeParser&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The final LlamaIndex parser breaks text down into sentences and then produces a Node for each sentence with a window of sentences to either side. You can choose how big the window is. Choosing a window size of one will produce Nodes with three sentences, the current sentence, one before and one after. A window size of two produces Nodes with five sentences, the current sentence, and two either side.&lt;/p&gt;
&lt;p&gt;This parser works on Documents as well; you use it like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { SentenceWindowNodeParser } from &quot;@llamaindex/core/node-parser&quot;;
import { Document } from &quot;@llamaindex/core/schema&quot;;
const text = loadText(); // get some text to split
const splitter = new SentenceWindowNodeParser({ windowSize: 3 });
const nodes = splitter.getNodesFromDocuments([new Document({ text })]);
const output = nodes.map((node) =&amp;gt; node.text);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;semantic-chunking&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/jparkerweb/semantic-chunking&quot;&gt;semantic-chunking&lt;/a&gt; is not a popular text splitter in the JavaScript world, but I wanted to include it as something that’s a bit different. It still gives you control over the maximum size of your chunks, but the way it splits up the chunks uses a more interesting method.&lt;/p&gt;
&lt;p&gt;It first splits the text into sentences, and then it generates embedding vectors for each sentence using a locally downloaded model (by default it uses &lt;a href=&quot;https://huggingface.co/Xenova/all-MiniLM-L6-v2&quot;&gt;Xenova/all-MiniLM-L6-v2&lt;/a&gt; but you can choose a different one if you want). It then groups the sentences into chunks based on how similar they are using &lt;a href=&quot;https://www.ibm.com/think/topics/cosine-similarity&quot;&gt;cosine similarity&lt;/a&gt;. The intention is to group sentences that go together and have related contents, and then, when the topic changes, start a new chunk.&lt;/p&gt;
&lt;p&gt;This is a smarter type of chunking than just splitting by chunk size, and likely even smarter than the markdown parsers that at least take section headings into account. The trade-off is that it is likely to be slower as there is more computation to be done.&lt;/p&gt;
&lt;p&gt;It is still simple to use though; install it with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install semantic-chunking
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can pass a &lt;code&gt;similarityThreshold&lt;/code&gt; to the &lt;code&gt;chunkit&lt;/code&gt; function, which is the minimum cosine similarity required for two sentences to be included in the same chunk. A high threshold provides a high bar for a sentence to be included and likely results in smaller chunks. A low threshold will allow for fewer and bigger chunks. As always, it’s worth experimenting with this setting to find what works for your data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { chunkit } from &quot;semantic-chunking&quot;;
const text = loadText(); // get some text to split
const chunks = chunkit(text, {
  maxTokenSize: 256,
  similarityThreshold: 0.5,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are other options around further combining chunks, check out &lt;a href=&quot;https://www.npmjs.com/package/semantic-chunking&quot;&gt;the documentation for more detail&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Unstructured&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://unstructured.io/&quot;&gt;Unstructured&lt;/a&gt; is a platform for extracting data from files and chunking it in a smart way. There is an &lt;a href=&quot;https://docs.unstructured.io/open-source/introduction/overview&quot;&gt;open-source toolkit&lt;/a&gt; for this, a &lt;a href=&quot;https://docs.unstructured.io/platform/overview&quot;&gt;no-code platform&lt;/a&gt;, and an &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/overview&quot;&gt;API&lt;/a&gt;. We&apos;re going to investigate the API here.&lt;/p&gt;
&lt;p&gt;The API has support for extracting data from loads of different file types, including images and documents like PDFs that may contain images or tables of data. Below is a simple example of calling the Unstructured API; you can read about more of the capabilities, particularly around extracting data from PDFs and images, in the &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/overview&quot;&gt;Unstructured API documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First you should install the API client:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install unstructured-client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need an API key. You can get a free API key, or the paid service has a two-week free trial. Once you have an API key, you can use the client to call the API.&lt;/p&gt;
&lt;p&gt;In this example, I am chunking text from a markdown file, but check out the &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/api-parameters&quot;&gt;API parameters&lt;/a&gt; you can use for behaviour with other types of file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { UnstructuredClient } from &quot;unstructured-client&quot;;
import { ChunkingStrategy } from &quot;unstructured-client/sdk/models/shared/index.js&quot;;
import { readFileSync } from &quot;node:fs&quot;;

const client = new UnstructuredClient({
  serverURL: &quot;https://api.unstructuredapp.io&quot;,
  security: {
    apiKeyAuth: &quot;YOUR_API_KEY_HERE&quot;,
  },
});

const data = readFileSync(&quot;./post.md&quot;);

const res = await client.general.partition({
  partitionParameters: {
    files: {
      content: data,
      fileName: &quot;post.md&quot;,
    },
    chunkingStrategy: ChunkingStrategy.BySimilarity,
  },
});
if (res.statusCode == 200) {
  console.log(res.elements);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;ll notice I selected a chunking strategy of similarity; you can read about the other &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/chunking&quot;&gt;chunking strategies available in the documentation&lt;/a&gt; as well as &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/partitioning&quot;&gt;partitioning strategies&lt;/a&gt;, which are used for documents like images and PDFs.&lt;/p&gt;
&lt;h2&gt;What&apos;s your chunking strategy?&lt;/h2&gt;
&lt;p&gt;It turns out that there are many options for splitting text up into chunks in the JavaScript ecosystem. From the quick and simple, like &lt;a href=&quot;https://github.com/golbin/llm-chunk&quot;&gt;llm-chunk&lt;/a&gt;, fully featured libraries with full GenAI pipelines like &lt;a href=&quot;https://js.langchain.com/&quot;&gt;LangChain&lt;/a&gt; or &lt;a href=&quot;https://ts.llamaindex.ai/&quot;&gt;LlamaIndex&lt;/a&gt;, and more complex methods like &lt;a href=&quot;https://github.com/jparkerweb/semantic-chunking&quot;&gt;semantic-chunking&lt;/a&gt; and the &lt;a href=&quot;https://docs.unstructured.io/api-reference/api-services/overview&quot;&gt;Unstructured API&lt;/a&gt; with all the options it brings for loading and splitting documents.&lt;/p&gt;
&lt;p&gt;Getting your chunking strategy right is important for getting good results from your RAG application, so ensure you read about the &lt;a href=&quot;https://unstructured.io/blog/chunking-for-rag-best-practices&quot;&gt;best practices&lt;/a&gt; and try out the available options to see the sort of results you get from them. If you want to experiment with the libraries llm-chunk, LangChain, LlamaIndex, and semantic-chunking, check out the example application, &lt;a href=&quot;https://chunkers.vercel.app/&quot;&gt;Chunkers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However you turn your text into chunks for your RAG application, it&apos;s good to understand all of the available options.&lt;/p&gt;
</description><pubDate>Wed, 18 Sep 2024 00:00:00 GMT</pubDate><category>javascript</category><category>nodejs</category><category>genai</category><category>chunking</category></item><item><title>How Using Fetch with the Streams API Gets You Faster UX with GenAI Apps</title><link>https://philna.sh/blog/2024/08/22/fetch-streams-api-for-faster-ux-generative-ai-apps/</link><guid isPermaLink="true">https://philna.sh/blog/2024/08/22/fetch-streams-api-for-faster-ux-generative-ai-apps/</guid><description>&lt;p&gt;Generative AI enables us to build incredible new types of applications, but large language model (LLM) responses can be slow. If we wait for the full response before updating the user interface, we might be making our users wait more than they need to. Thankfully, most LLM APIs—including OpenAI, Anthropic, and &lt;a href=&quot;https://langflow.org&quot;&gt;Langflow&lt;/a&gt; provide streaming endpoints that you can use to stream responses as they are generated. In this post, we&apos;re going to see how to use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;JavaScript&apos;s &lt;code&gt;fetch&lt;/code&gt; API&lt;/a&gt; to immediately update your front-end application as an LLM generates output and create a better user experience.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This post was originally written for DataStax, but didn&apos;t survive a content migration as part of &amp;lt;a href=&quot;https://www.ibm.com/new/announcements/ibm-to-acquire-datastax-helping-clients-bring-the-power-of-unstructured-data-to-enterprise-ai-applications&quot;&amp;gt;IBM&apos;s purchase&amp;lt;/a&amp;gt;. I thought the content was useful, so have republished it here.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;Slow responses without streaming&lt;/h2&gt;
&lt;p&gt;Let&apos;s start with an example to show what a slow result looks like to a user. &lt;a href=&quot;https://fetch-streaming.vercel.app/&quot;&gt;You can try it here&lt;/a&gt;. This &lt;a href=&quot;https://github.com/philnash/fetch-streaming&quot;&gt;example GitHub repo&lt;/a&gt; demonstrates an Express application that serves up static files and has one endpoint that streams some &lt;a href=&quot;https://en.wikipedia.org/wiki/Lorem_ipsum&quot;&gt;lorem ipsum&lt;/a&gt; text to the front-end. The following code is how the server streams the response:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.get(&quot;/stream&quot;, async (_req, res) =&amp;gt; {
  res.set(&quot;Content-Type&quot;, &quot;text/plain&quot;);
  res.set(&quot;Transfer-Encoding&quot;, &quot;chunked&quot;);

  const textChunks = text.replace(/\n/g, &quot;&amp;lt;br&amp;gt;&quot;).split(/(?&amp;lt;=\.)/g);

  for (let chunk of textChunks) {
    res.write(chunk);
    await sleep(250);
  }

  res.end();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Content-Type&lt;/code&gt; header shows that we are returning text and the &lt;code&gt;Transfer-Encoding&lt;/code&gt; header tells the browser that the text will be arriving as a stream of chunks.&lt;/p&gt;
&lt;p&gt;Express enables you to write content to the response at any time, using &lt;code&gt;res.write&lt;/code&gt;. In this case, we break a section of text up into sentences and then write each sentence after a 250 millisecond gap. This is standing in for our streaming LLM response, as it is simpler than putting together an entire GenAI application to demonstrate this.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at how we would normally implement a fetch request to get some data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const response = await fetch(&quot;/stream&quot;);
const text = await response.text();
output.innerHTML = text;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The text function collects the entire response then decodes it into text. In this example, we write the text to the page. This is fine if the response is fast, but our example server above is returning a new chunk of text every quarter of a second, and it will depend on how long the response is as to how long it takes to resolve &lt;code&gt;response.text()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Streaming with fetch&lt;/h2&gt;
&lt;p&gt;Setting up to stream a response from a server is not as straightforward, but the results are worth it. We&apos;re going to use the fact that the body of a response is a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream&quot;&gt;&lt;code&gt;ReadableStream&lt;/code&gt;&lt;/a&gt;. We can then set up a streaming pipeline that decodes the incoming stream and writes it to the page.&lt;/p&gt;
&lt;p&gt;We need to decode the stream because the chunks of stream we receive are bytes in the form of a &lt;code&gt;Uint8Array&lt;/code&gt;. We want them as text, so we can use a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream&quot;&gt;&lt;code&gt;TextDecoderStream&lt;/code&gt;&lt;/a&gt; to decode the bytes as they flow through. The &lt;code&gt;TextDecoderStream&lt;/code&gt; is an implementation of a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/TransformStream&quot;&gt;&lt;code&gt;TransformStream&lt;/code&gt;&lt;/a&gt;; it has both a readable and writable stream, so it can be used in streaming pipelines.&lt;/p&gt;
&lt;p&gt;We then want to write the text to the page, so we can build a custom &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WritableStream&quot;&gt;&lt;code&gt;WritableStream&lt;/code&gt;&lt;/a&gt; to handle that. Let&apos;s see the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const response = await fetch(&quot;/stream&quot;);
const decoderStream = new TextDecoderStream(&quot;utf-8&quot;);
const writer = new WritableStream({
  write(chunk) {
    output.innerHTML += chunk;
  },
});

response.body.pipeThrough(decoderStream).pipeTo(writer);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now let&apos;s compare this version side-by-side to the version that waits for the whole response before it decodes and writes it to the page.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;As you can see, the text takes the same amount of time to load whether we use the streaming version or the regular version, but &lt;strong&gt;users can start reading the response much earlier&lt;/strong&gt; when it streams straight to the page.&lt;/p&gt;
&lt;p&gt;When you are dealing with LLMs, or other responses that progressively generate a response, streaming the response and rendering it to the page as you receive it gives users a much better perceived performance over waiting for the full response.&lt;/p&gt;
&lt;h2&gt;More to know about streams&lt;/h2&gt;
&lt;p&gt;There&apos;s more to streams than the code above. Our basic implementation of a &lt;code&gt;WritableStream&lt;/code&gt; above included a &lt;code&gt;write&lt;/code&gt; function that is called when new chunks of data are ready to be written. You can also define the following functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start&lt;/code&gt;: called as soon as the stream is constructed and used to set up any resources needed for writing the data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close&lt;/code&gt;: called once there are no more chunks to write to the stream and used to release any resources&lt;/li&gt;
&lt;li&gt;&lt;code&gt;abort&lt;/code&gt;: called if the application signals that the stream should be immediately closed and, like &lt;code&gt;close&lt;/code&gt;, used to clean up any resources. Unlike &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;abort&lt;/code&gt; will run even if there are chunks still to be written and cause them to be thrown away.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also pass a &lt;code&gt;queuingStrategy&lt;/code&gt; to a &lt;code&gt;WritableStream&lt;/code&gt; that allows you to control how fast the stream receives data. This is known as &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts#backpressure&quot;&gt;&quot;backpressure,&quot; and you can read more about it on MDN&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Catching errors&lt;/h3&gt;
&lt;p&gt;If there is an error in the stream, like a break in the connection between the front-end and the server, you will need to catch the error. The stream method &lt;code&gt;pipeTo&lt;/code&gt; is the final method that is called in a pipeline and returns a &lt;code&gt;Promise&lt;/code&gt;. You can either catch errors with the &lt;code&gt;catch&lt;/code&gt; method on the &lt;code&gt;Promise&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;response.body
  .pipeThrough(decoderStream)
  .pipeTo(writer)
  .catch((error) =&amp;gt; {
    console.log(&quot;Something went wrong with the stream!&quot;);
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can await the result and use a &lt;code&gt;try/catch&lt;/code&gt; block:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try {
  await response.body
    .pipeThrough(decoderStream)
    .pipeTo(writer);
} catch((error) =&amp;gt; {
  console.log(&quot;Something went wrong with the stream!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Server-sent events&lt;/h3&gt;
&lt;p&gt;In the example above, the stream from the server was just text. Many LLM APIs, including &lt;a href=&quot;https://docs.anthropic.com/en/api/messages-streaming&quot;&gt;Anthropic&lt;/a&gt;, &lt;a href=&quot;https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=rest#stream_generate_content&quot;&gt;Google&lt;/a&gt;, &lt;a href=&quot;https://platform.openai.com/docs/api-reference/streaming&quot;&gt;OpenAI&lt;/a&gt;, and &lt;a href=&quot;https://langflow.org&quot;&gt;Langflow&lt;/a&gt;, send more data back than just the text response and use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events&quot;&gt;server-sent events standard&lt;/a&gt; to format those streams.&lt;/p&gt;
&lt;p&gt;To send data as server-sent events we&apos;d need to change a couple of things from our original server. The &lt;code&gt;Content-Type&lt;/code&gt; becomes &lt;code&gt;text/event-stream&lt;/code&gt;. Instead of sending plain text, each message must be labeled as either &quot;event,&quot; &quot;data,&quot; &quot;id,&quot; or &quot;retry,&quot; and messages are separated by two newlines. Taking the server side example from earlier, we could update it like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.get(&quot;/stream&quot;, async (_req, res) =&amp;gt; {
  res.set(&quot;Content-Type&quot;, &quot;text/event-stream&quot;);
  res.set(&quot;Transfer-Encoding&quot;, &quot;chunked&quot;);

  const textChunks = text.replace(/\n/g, &quot;&amp;lt;br&amp;gt;&quot;).split(/(?&amp;lt;=\.)/g);

  for (let chunk of textChunks) {
    res.write(`event: message\ndata: ${chunk}\n\n`);
    await sleep(250);
  }

  res.end();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now each message is of the form:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;event: message
data: ${textChunk}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which means we need to parse this on the client side. Normally, this is easily done by making a connection to the server using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EventSource&quot;&gt;&lt;code&gt;EventSource&lt;/code&gt;&lt;/a&gt; object and letting the browser parse the messages into events. If you are building with GenAI, you&apos;re most likely sending user queries to the server over a POST request, which is not supported by &lt;code&gt;EventSource&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To use server-sent events with a POST request, you’ll need to parse the responses. One way to do this is to use the &lt;a href=&quot;https://www.npmjs.com/package/eventsource-parser&quot;&gt;eventsource-parser module&lt;/a&gt;, which even makes a transform stream available; this fits in nicely with our existing application.&lt;/p&gt;
&lt;p&gt;To handle server-sent events in our existing front-end, we can import an &lt;code&gt;EventSourceParserStream&lt;/code&gt; from &lt;code&gt;eventsource-parser/stream&lt;/code&gt; and use it in our pipeline.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const response = await fetch(&quot;/stream&quot;);
const decoderStream = new TextDecoderStream(&quot;utf-8&quot;);
const parserStream = new EventSourceParserStream();
const writer = new WritableStream({
  write(event) {
    output.innerHTML += event.data;
  },
});

response.body
  .pipeThrough(decoderStream)
  .pipeThrough(parserStream)
  .pipeTo(writer);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that now we get an event emitted from the stream that has a data property containing the data sent from the server. This is still text in this application, but you could send, for example, a JSON object containing structured data instead. You can see &lt;a href=&quot;https://github.com/philnash/fetch-streaming?tab=readme-ov-file#server-sent-events&quot;&gt;this implementation of server-sent event streaming in the example GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Async iterables&lt;/h3&gt;
&lt;p&gt;There is an easier way to handle the incoming chunks of a streaming response using a &lt;code&gt;for await...of&lt;/code&gt; loop.&lt;/p&gt;
&lt;p&gt;In this case we don&apos;t need to create our own &lt;code&gt;WritableStream&lt;/code&gt;, though we do still need to use a &lt;code&gt;TextDecoder&lt;/code&gt; to decode the bytes into a string. It relies on the body of the &lt;code&gt;response&lt;/code&gt; being a &lt;code&gt;ReadableStream&lt;/code&gt; that implements the async iterable protocol, and looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const decoder = new TextDecoder();
for await (const chunk of response.body) {
  const text = decoder.decode(chunk, { stream: true });
  streamOutput.innerHTML += text;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sadly, &lt;a href=&quot;https://caniuse.com/mdn-api_response_body_readable_byte_stream&quot;&gt;Safari does not support this technique&lt;/a&gt;, so we have to avoid it in the front-end for now.&lt;/p&gt;
&lt;h2&gt;Get comfortable with streaming&lt;/h2&gt;
&lt;p&gt;Understanding how to stream a response from a web server and consume it on the client is vital to creating a great user experience when working with LLMs. Using &lt;code&gt;fetch&lt;/code&gt; as a &lt;code&gt;ReadableStream&lt;/code&gt; is the web-native way to do so. When you stream a potentially slow text response, you can improve the perceived performance of your application, leading to happier users and customers&lt;/p&gt;
&lt;p&gt;If you&apos;re playing with &lt;a href=&quot;https://langflow.org&quot;&gt;Langflow&lt;/a&gt;, you&apos;ll find that you can stream responses from the API, and you should take advantage of this.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat&quot;&gt;Vercel&apos;s AI SDK uses fetch to make streaming easier for a number of frameworks with the useChat hook&lt;/a&gt;. For React Server Component users, you can also stream UI using &lt;a href=&quot;https://sdk.vercel.ai/docs/ai-sdk-rsc/streaming-values&quot;&gt;Vercel&apos;s AI RSC API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to play around with streaming, you can check out &lt;a href=&quot;https://github.com/philnash/fetch-streaming&quot;&gt;the example code from this blog post on GitHub&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 22 Aug 2024 00:00:00 GMT</pubDate><category>javascript</category><category>nodejs</category><category>fetch</category></item><item><title>JavaScript is getting array grouping methods</title><link>https://philna.sh/blog/2023/09/14/javascript-array-grouping-methods/</link><guid isPermaLink="true">https://philna.sh/blog/2023/09/14/javascript-array-grouping-methods/</guid><description>&lt;p&gt;Grouping items in an array is one of those things you&apos;ve probably done a load of times. Each time you would have written a grouping function by hand or perhaps reached for &lt;a href=&quot;https://lodash.com/docs/4.17.15#groupBy&quot;&gt;lodash&apos;s &lt;code&gt;groupBy&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;
&lt;p&gt;The good news is that JavaScript is now getting grouping methods so you won&apos;t have to anymore. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy&quot;&gt;&lt;code&gt;Object.groupBy&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy&quot;&gt;&lt;code&gt;Map.groupBy&lt;/code&gt;&lt;/a&gt; are new methods that will make grouping easier and save us time or a dependency.&lt;/p&gt;
&lt;h2&gt;Grouping until now&lt;/h2&gt;
&lt;p&gt;Let&apos;s say you have an array of objects representing people and you want to group them by their age. You might use a &lt;code&gt;forEach&lt;/code&gt; loop like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const people = [
  { name: &quot;Alice&quot;, age: 28 },
  { name: &quot;Bob&quot;, age: 30 },
  { name: &quot;Eve&quot;, age: 28 },
];

const peopleByAge = {};

people.forEach((person) =&amp;gt; {
  const age = person.age;
  if (!peopleByAge[age]) {
    peopleByAge[age] = [];
  }
  peopleByAge[age].push(person);
});
console.log(peopleByAge);
/*
{
  &quot;28&quot;: [{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:28}, {&quot;name&quot;:&quot;Eve&quot;,&quot;age&quot;:28}],
  &quot;30&quot;: [{&quot;name&quot;:&quot;Bob&quot;,&quot;age&quot;:30}]
}
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you may choose to use &lt;code&gt;reduce&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const peopleByAge = people.reduce((acc, person) =&amp;gt; {
  const age = person.age;
  if (!acc[age]) {
    acc[age] = [];
  }
  acc[age].push(person);
  return acc;
}, {});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Either way, it&apos;s slightly awkward code. You always have to check the object to see whether the grouping key exists and if not, create it with an empty array. Then you can push the item into the array.&lt;/p&gt;
&lt;h2&gt;Grouping with Object.groupBy&lt;/h2&gt;
&lt;p&gt;With the new &lt;code&gt;Object.groupBy&lt;/code&gt; method, you can outcome like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const peopleByAge = Object.groupBy(people, (person) =&amp;gt; person.age);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much simpler! Though there are some things to be aware of.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Object.groupBy&lt;/code&gt; returns a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects&quot;&gt;null-prototype object&lt;/a&gt;. This means the the object does not inherit any properties from &lt;code&gt;Object.prototype&lt;/code&gt;. This is great because it means you won&apos;t accidentally overwrite any properties on &lt;code&gt;Object.prototype&lt;/code&gt;, but it also means that the object doesn&apos;t have any of the methods you might expect, like &lt;code&gt;hasOwnProperty&lt;/code&gt; or &lt;code&gt;toString&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const peopleByAge = Object.groupBy(people, (person) =&amp;gt; person.age);
console.log(peopleByAge.hasOwnProperty(&quot;28&quot;));
// TypeError: peopleByAge.hasOwnProperty is not a function
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The callback function you pass to &lt;code&gt;Object.groupBy&lt;/code&gt; should return a &lt;code&gt;string&lt;/code&gt; or a &lt;code&gt;Symbol&lt;/code&gt;. If it returns anything else, it will be coerced to a &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In our example, we have been returning the &lt;code&gt;age&lt;/code&gt; as a &lt;code&gt;number&lt;/code&gt;, but in the result it is coerced to &lt;code&gt;string&lt;/code&gt;. Though you can still access the properties using a &lt;code&gt;number&lt;/code&gt; as using square bracket notation will also coerce the argument to &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(peopleByAge[28]);
// =&amp;gt; [{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:28}, {&quot;name&quot;:&quot;Eve&quot;,&quot;age&quot;:28}]
console.log(peopleByAge[&quot;28&quot;]);
// =&amp;gt; [{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:28}, {&quot;name&quot;:&quot;Eve&quot;,&quot;age&quot;:28}]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Grouping with Map.groupBy&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Map.groupBy&lt;/code&gt; does almost the same thing as &lt;code&gt;Object.groupBy&lt;/code&gt; except it returns a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map&quot;&gt;&lt;code&gt;Map&lt;/code&gt;&lt;/a&gt;. This means that you can use all the usual &lt;code&gt;Map&lt;/code&gt; functions. It also means that you can return any type of value from the callback function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ceo = { name: &quot;Jamie&quot;, age: 40, reportsTo: null };
const manager = { name: &quot;Alice&quot;, age: 28, reportsTo: ceo };

const people = [
  ceo,
  manager,
  { name: &quot;Bob&quot;, age: 30, reportsTo: manager },
  { name: &quot;Eve&quot;, age: 28, reportsTo: ceo },
];

const peopleByManager = Map.groupBy(people, (person) =&amp;gt; person.reportsTo);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, we are grouping people by who they report to. Note that to retrieve items from this &lt;code&gt;Map&lt;/code&gt; by an object, the objects have to have the same identity.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;peopleByManager.get(ceo);
// =&amp;gt; [{ name: &quot;Alice&quot;, age: 28, reportsTo: ceo }, { name: &quot;Eve&quot;, age: 28, reportsTo: ceo }]
peopleByManager.get({ name: &quot;Jamie&quot;, age: 40, reportsTo: null });
// =&amp;gt; undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the above example, the second line uses an object that looks like the &lt;code&gt;ceo&lt;/code&gt; object, but it is not the same object so it doesn&apos;t return anything from the &lt;code&gt;Map&lt;/code&gt;. To retrieve items successfully from the &lt;code&gt;Map&lt;/code&gt;, make sure you keep a reference to the object you want to use as the key.&lt;/p&gt;
&lt;h2&gt;When will this be available?&lt;/h2&gt;
&lt;p&gt;The two &lt;code&gt;groupBy&lt;/code&gt; methods are part of a &lt;a href=&quot;https://github.com/tc39/proposal-array-grouping&quot;&gt;TC39 proposal that is currently at stage 3&lt;/a&gt;. This means that there is a good chance it will become a standard and, as such, there are implementations appearing.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.chrome.com/en/blog/new-in-chrome-117/#array-grouping&quot;&gt;Chrome 117 just launched with support for these two methods&lt;/a&gt; and Firefox released support in version 119. Safari had implemented these methods under different names, I&apos;m sure they will update that soon. As the methods are in Chrome that means they have been implemented in V8, so will be available in Node the next time V8 is updated.&lt;/p&gt;
&lt;h2&gt;Why use static methods?&lt;/h2&gt;
&lt;p&gt;You might wonder why this is being implemented as &lt;code&gt;Object.groupBy&lt;/code&gt; and not &lt;code&gt;Array.prototype.groupBy&lt;/code&gt;. &lt;a href=&quot;https://github.com/tc39/proposal-array-grouping#why-static-methods&quot;&gt;According to the proposal&lt;/a&gt; there is a library that used to monkey patch the &lt;code&gt;Array.prototype&lt;/code&gt; with an incompatible &lt;code&gt;groupBy&lt;/code&gt; method. When considering new APIs for the web, backwards compatibility is hugely important. This was highligted a few years ago when trying to implement &lt;code&gt;Array.prototype.flatten&lt;/code&gt;, in an event known as &lt;a href=&quot;https://developer.chrome.com/blog/smooshgate/&quot;&gt;SmooshGate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, using static methods actually seems better for future extensibility. When the &lt;a href=&quot;https://github.com/tc39/proposal-record-tuple&quot;&gt;Records and Tuples proposal&lt;/a&gt; comes to fruition, we can add a &lt;code&gt;Record.groupBy&lt;/code&gt; method for grouping arrays into an immutable record.&lt;/p&gt;
&lt;h2&gt;JavaScript is filling in the gaps&lt;/h2&gt;
&lt;p&gt;Grouping items together is clearly an important thing we do as developers. &lt;a href=&quot;https://www.npmjs.com/package/lodash.groupby&quot;&gt;&lt;code&gt;lodash.groupBy&lt;/code&gt; is currently downloaded from npm&lt;/a&gt; between 1.5 and 2 million times a week. It&apos;s great to see JavaScript filling in these gaps and making it easier for us to do our jobs.&lt;/p&gt;
&lt;p&gt;For now, go get Chrome 117 and try these new methods out for yourself.&lt;/p&gt;
</description><pubDate>Thu, 14 Sep 2023 00:00:00 GMT</pubDate><category>javascript</category></item><item><title>Node.js includes built-in support for .env files</title><link>https://philna.sh/blog/2023/09/05/nodejs-supports-dotenv/</link><guid isPermaLink="true">https://philna.sh/blog/2023/09/05/nodejs-supports-dotenv/</guid><description>&lt;p&gt;With the &lt;a href=&quot;https://nodejs.org/en/blog/release/v20.6.0&quot;&gt;recent release of version 20.6.0&lt;/a&gt;, Node.js now has built-in support for &lt;code&gt;.env&lt;/code&gt; files. You can now load environment variables from a &lt;code&gt;.env&lt;/code&gt; file into &lt;code&gt;process.env&lt;/code&gt; in your Node.js application completely dependency-free.&lt;/p&gt;
&lt;p&gt;Loading an &lt;code&gt;.env&lt;/code&gt; file is now as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node --env-file .env
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What is .env?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; files are used to configure environment variables that will be present within a running application. The idea comes from the &lt;a href=&quot;https://12factor.net/&quot;&gt;Twelve-Factor App methodology&lt;/a&gt;, which says to &lt;a href=&quot;https://12factor.net/config&quot;&gt;store everything that is likely to vary between deploys (e.g. dev, staging, production) in the environment&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Config should not be a part of your application code and should not be checked-in to version control. Things like API credentials, or other secrets, should be stored separately and loaded in the environment in which they are needed. A &lt;code&gt;.env&lt;/code&gt; file lets you manage your config for applications where it isn&apos;t practical to set variables in the environment, like your development machine or &amp;lt;abbr title=&quot;continous integration&quot;&amp;gt;CI&amp;lt;/abbr&amp;gt;.&lt;/p&gt;
&lt;p&gt;There are libraries in many different languages that support using a &lt;code&gt;.env&lt;/code&gt; file to load variables into the environment, they are usually called &quot;dotenv&quot;, and the &lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;Node.js dotenv&lt;/a&gt; is no different. But now, Node.js itself supports this behaviour.&lt;/p&gt;
&lt;h2&gt;How do you use .env in Node.js?&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;.env&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PASSWORD=supersecret
API_KEY=84de8263ccad4d3dabba0754e3c68b7a
# .env files can have comments too
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By convention you would save this as &lt;code&gt;.env&lt;/code&gt; in the root of your project, though you can call it whatever you want.&lt;/p&gt;
&lt;p&gt;You can then set the variables in the file as environment variables by starting Node.js with the &lt;code&gt;--env-file&lt;/code&gt; flag pointing to your &lt;code&gt;.env&lt;/code&gt; file. When loaded, the variables are available as properties of &lt;code&gt;process.env&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ node --env-file .env
Welcome to Node.js v20.6.0.
Type &quot;.help&quot; for more information.
&amp;gt; console.log(process.env.PASSWORD)
supersecret
undefined
&amp;gt; console.log(process.env.API_KEY)
84de8263ccad4d3dabba0754e3c68b7a
undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Supported features&lt;/h3&gt;
&lt;p&gt;Support right now is fairly basic compared to &lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;dotenv&lt;/a&gt;. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You cannot currently use &lt;a href=&quot;https://github.com/motdotla/dotenv#multiline-values&quot;&gt;multiline values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;You cannot use &lt;a href=&quot;https://github.com/motdotla/dotenv-expand&quot;&gt;variable expansion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, the feature is under active development. Since the 20.7.0 release, you can now specify multiple files. The variables from the last file will override any previous files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node --env-file .env --env-file .env.development
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is more work to be done, and some of these features may be added. You can &lt;a href=&quot;https://github.com/nodejs/node/issues/49148&quot;&gt;follow the discussion on GitHub here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Incorrect features&lt;/h3&gt;
&lt;p&gt;In the 20.6.0 release, &lt;a href=&quot;https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#--env-fileconfig&quot;&gt;the documentation says&lt;/a&gt;, &quot;If the same variable is defined in the environment and in the file, the value from the environment takes precedence.&quot; This is the way that all dotenv packages work by default. However, that is not currently true of Node.js&apos;s implementation and variables in the &lt;code&gt;.env&lt;/code&gt; file will override the environment.&lt;/p&gt;
&lt;p&gt;This has been fixed as of version 20.7.0. Variables defined in the environment now take precedence over variables in a &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;h3&gt;Benefits to Node.js&apos;s implementation&lt;/h3&gt;
&lt;p&gt;Even though this implementation is missing some features, it has some benefits over using a third-party package. Node.js loads and parses the &lt;code&gt;.env&lt;/code&gt; file as it is starting up, so you can include &lt;a href=&quot;https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#environment-variables&quot;&gt;environment variables that configure Node.js itself&lt;/a&gt;, like &lt;a href=&quot;https://nodejs.org/dist/latest-v20.x/docs/api/cli.html#node_optionsoptions&quot;&gt;&lt;code&gt;NODE_OPTIONS&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, you can have an &lt;code&gt;.env&lt;/code&gt; file that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NODE_OPTIONS=&quot;--no-warnings --inspect=127.0.0.1:9229&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when you run &lt;code&gt;node --env-file=.env&lt;/code&gt; the process will run without emitting warnings and it will activate the inspector on the IP address &lt;code&gt;127.0.0.1:9229&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: you cannot put &lt;code&gt;NODE_OPTIONS=&quot;--env-file .env&lt;/code&gt; in your &lt;code&gt;.env&lt;/code&gt; file. It is disallowed to avoid inifinite loops.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Node.js just keeps improving&lt;/h2&gt;
&lt;p&gt;Go try out Node.js version 20.6.0! Version 20 has brought new features, like a &lt;a href=&quot;https://www.sonarsource.com/blog/node-js-test-runner/&quot;&gt;stable test runner&lt;/a&gt;, &lt;a href=&quot;https://nodejs.org/en/blog/release/v20.4.0&quot;&gt;mock timers&lt;/a&gt;, and now &lt;code&gt;.env&lt;/code&gt; file support, as well as many other upgrades, fixes and improvements. &lt;a href=&quot;https://github.com/nodejs/Release#release-schedule&quot;&gt;Version 20 becomes the active &amp;lt;abbr title=&quot;long term support&quot;&amp;gt;LTS&amp;lt;/abbr&amp;gt; version of Node.js in October&lt;/a&gt;, so now is a good time to test these new features out and start considering upgrading your application to take advantage.&lt;/p&gt;
</description><pubDate>Tue, 05 Sep 2023 00:00:00 GMT</pubDate><category>typescript</category><category>javascript</category><category>node</category><category>dotenv</category></item><item><title>Easy and accessible pagination links for your Astro collections</title><link>https://philna.sh/blog/2023/07/13/easy-and-accessible-pagination-links-for-your-astro-collections/</link><guid isPermaLink="true">https://philna.sh/blog/2023/07/13/easy-and-accessible-pagination-links-for-your-astro-collections/</guid><description>&lt;p&gt;Generating pagination links is not as straightforward as it may seem. So, while rebuilding my own site with Astro, I released a &lt;a href=&quot;https://www.npmjs.com/package/@philnash/astro-pagination&quot;&gt;&lt;code&gt;&amp;lt;Pagination /&amp;gt;&lt;/code&gt; component on npm as @philnash/astro-pagination&lt;/a&gt; that anyone can use in their Astro site. Read on to find out more.&lt;/p&gt;
&lt;h2&gt;Pagination&lt;/h2&gt;
&lt;p&gt;Pagination is something that most content sites need. It is often better to list collections with lots of entries, like blog posts, across multiple pages because a single page would be overwhelming to scroll through.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; provides the &lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#paginate&quot;&gt;&lt;code&gt;paginate&lt;/code&gt; function&lt;/a&gt; to the callback to &lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#getstaticpaths&quot;&gt;&lt;code&gt;getStaticPaths&lt;/code&gt;&lt;/a&gt; to make it easy to turn a collection into a number of paths and pages. Once you have turned your collection into a list of pages, you then need to render links that your users can use to navigate through the list.&lt;/p&gt;
&lt;p&gt;While this may at first seem straightforward, as with many things on the web, there are hidden depths to it. Not only do you need to write the code that parses the current page and produces links to the previous page, the next page and a window of pages around the current one, you also need to produce accessible HTML that will be easy to use for any of your site&apos;s visitors.&lt;/p&gt;
&lt;h2&gt;An Astro Pagination component&lt;/h2&gt;
&lt;p&gt;To make this easy for anyone building with Astro, I released &lt;a href=&quot;https://www.npmjs.com/package/@philnash/astro-pagination&quot;&gt;@philnash/astro-pagination on npm&lt;/a&gt;. It is a &lt;code&gt;&amp;lt;Pagination /&amp;gt;&lt;/code&gt; component that you can use in your Astro site to create pagination links based on a &lt;code&gt;Page&lt;/code&gt; object.&lt;/p&gt;
&lt;h3&gt;How to use it&lt;/h3&gt;
&lt;p&gt;Start by installing the package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @philnash/astro-pagination
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in a list page, you can import and use the &lt;code&gt;Pagination&lt;/code&gt; component. The component requires two properties, a &lt;code&gt;page&lt;/code&gt;  and a &lt;code&gt;urlPattern&lt;/code&gt;. The &lt;code&gt;page&lt;/code&gt; should be an &lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#the-pagination-page-prop&quot;&gt;Astro &lt;code&gt;Page&lt;/code&gt;&lt;/a&gt; object, typically provided through &lt;code&gt;Astro.props&lt;/code&gt;. The &lt;code&gt;urlPattern&lt;/code&gt; should be the path you are using for your paginated pages, with a &lt;code&gt;{}&lt;/code&gt; where the page number should go. A simplified Astro page might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import Pagination from &quot;@philnash/astro-pagination&quot;;

export async function getStaticPaths({ paginate }) { /* ... */ }
const { page } = Astro.props;
---

{ /* render the items from the page */ }

&amp;lt;Pagination page={page} urlPattern=&quot;/blog/page/{}&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will render HTML that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;nav role=&quot;navigation&quot; aria-label=&quot;Pagination&quot;&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a
        href=&quot;/blog/page/4&quot;
        class=&quot;previous-page&quot;
        aria-label=&quot;Go to previous page&quot;
        &amp;gt;« Prev&amp;lt;/a
      &amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page&quot; aria-label=&quot;Go to page 1&quot;&amp;gt; 1 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;span class=&quot;start-ellipsis&quot;&amp;gt;…&amp;lt;/span&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page/3&quot; aria-label=&quot;Go to page 3&quot;&amp;gt; 3 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page/4&quot; aria-label=&quot;Go to page 4&quot;&amp;gt; 4 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;em aria-current=&quot;page&quot; aria-label=&quot;Current page, page 5&quot;&amp;gt; 5 &amp;lt;/em&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page/6&quot; aria-label=&quot;Go to page 6&quot;&amp;gt; 6 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page/7&quot; aria-label=&quot;Go to page 7&quot;&amp;gt; 7 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;span class=&quot;end-ellipsis&quot;&amp;gt;…&amp;lt;/span&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a class=&quot;number&quot; href=&quot;/blog/page/10&quot; aria-label=&quot;Go to page 10&quot;&amp;gt; 10 &amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a href=&quot;/blog/page/6&quot; class=&quot;next-page&quot; aria-label=&quot;Go to next page&quot;
        &amp;gt;Next »&amp;lt;/a
      &amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This renders like this:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Well, it renders like that with my site&apos;s CSS applied. You will need to style it yourself.&lt;/p&gt;
&lt;p&gt;The generated markup includes a bunch of things, including accessibility features based on &lt;a href=&quot;https://www.a11ymatters.com/pattern/pagination/&quot;&gt;research from a11ymatters on accessible pagination&lt;/a&gt;. There is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a link to the previous page&lt;/li&gt;
&lt;li&gt;a link to the first page&lt;/li&gt;
&lt;li&gt;a window of links around the current page&lt;/li&gt;
&lt;li&gt;ellipses to show where pages exist between the first/last page and the current window&lt;/li&gt;
&lt;li&gt;a link to the last page&lt;/li&gt;
&lt;li&gt;a link to the next page&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; element around the links, with a &lt;code&gt;role&lt;/code&gt; attribute set to &quot;navigation&quot; and an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label&quot;&gt;&lt;code&gt;aria-label&lt;/code&gt; attribute&lt;/a&gt; to describe it as &quot;Pagination&quot;&lt;/li&gt;
&lt;li&gt;a list of links, allowing assistive technology to announce how many items there are in the list and navigate through them&lt;/li&gt;
&lt;li&gt;an &lt;code&gt;aria-label&lt;/code&gt; attribute on each link to provide a full description of the link&apos;s destination&lt;/li&gt;
&lt;li&gt;an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current&quot;&gt;&lt;code&gt;aria-current&lt;/code&gt; attribute&lt;/a&gt; on the element representing the current page&lt;/li&gt;
&lt;li&gt;a helpful class name on each of the important elements to allow for styling&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Advanced usage&lt;/h3&gt;
&lt;p&gt;There are more properties you can pass to the &lt;code&gt;&amp;lt;Pagination /&amp;gt;&lt;/code&gt; component that give you greater control over the output. They include properties like &lt;code&gt;previousLabel&lt;/code&gt; and &lt;code&gt;nextLabel&lt;/code&gt;, that lets you set the text for the previous and next links, or &lt;code&gt;windowSize&lt;/code&gt;, that lets you determine how many pages are shown in the middle of the range. You can &lt;a href=&quot;https://github.com/philnash/astro-components/tree/main/packages/astro-pagination#options-and-advanced-usage&quot;&gt;see all the the available options in the documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Future improvements&lt;/h2&gt;
&lt;p&gt;While the &lt;code&gt;&amp;lt;Pagination /&amp;gt;&lt;/code&gt; component is ready to be used, and is already in use on my own site, there are definitely some improvements that I will be adding. For example, you should be able to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;use a component for the &lt;code&gt;previousLabel&lt;/code&gt; and &lt;code&gt;nextLabel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;style the links like other Astro components&lt;/li&gt;
&lt;li&gt;add class names to the elements, so you can style using utility CSS frameworks&lt;/li&gt;
&lt;li&gt;handle internationalisation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ultimately, I&apos;d love for this to be a component that every Astro site considers using for pagination, from my blog right here to the &lt;a href=&quot;https://astro.build/blog/&quot;&gt;Astro blog itself&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Any feedack?&lt;/h2&gt;
&lt;p&gt;I tried to make this component simple to use and flexible. As I&apos;ve described above, there&apos;s plenty more to do, but it&apos;s always worth asking for feedback too.&lt;/p&gt;
&lt;p&gt;So, would you use this component to simplify pagination in your own Astro sites? Is there anything you&apos;d add or change? Let me know &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;on Twitter&lt;/a&gt;, another &lt;a href=&quot;/links&quot;&gt;social network of choice&lt;/a&gt;, or in &lt;a href=&quot;https://github.com/philnash/astro-components/issues&quot;&gt;the GitHub issues&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Astro Pagination&lt;/h2&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://github.com/philnash/astro-components/tree/main/packages/astro-pagination&quot;&gt;the source on GitHub&lt;/a&gt; (give it a star, if you fancy) and let me know if you use this component.&lt;/p&gt;
</description><pubDate>Thu, 13 Jul 2023 00:00:00 GMT</pubDate><category>typescript</category><category>javascript</category><category>astro</category><category>pagination</category></item><item><title>Build bots on Bluesky with Node.js and GitHub Actions</title><link>https://philna.sh/blog/2023/05/01/build-bots-on-bluesky-with-typescript/</link><guid isPermaLink="true">https://philna.sh/blog/2023/05/01/build-bots-on-bluesky-with-typescript/</guid><description>&lt;p&gt;&lt;a href=&quot;https://bsky.app/&quot;&gt;Bluesky&lt;/a&gt; is the new social network in town and it&apos;s an exciting place to explore right now. I was fortunate enough to get an invite early on and take part in the early community. But Bluesky is not just a Twitter clone, it&apos;s an application on top of &lt;a href=&quot;https://atproto.com/&quot;&gt;The AT Protocol&lt;/a&gt;, a (still being built) federated protocol for social networks with some interesting properties.&lt;/p&gt;
&lt;p&gt;Because there&apos;s a protocol, that also means there&apos;s an API. If you remember far enough back to the early days of Twitter, the API drove a lot of exciting applications and features for users. There was a swarm of activity around Twitter&apos;s API and now, even in the early days, there is a similar excitement about Bluesky&apos;s API. So let&apos;s look into how to get started with the API using TypeScript and build a bot that runs on a schedule using &lt;a href=&quot;https://docs.github.com/en/actions&quot;&gt;GitHub Actions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What you will need&lt;/h2&gt;
&lt;p&gt;In order to build against the Bluesky API you will need an account. As I write this, accounts are invite only, but as the application and protocol stabilises, I expect there to me more available.&lt;/p&gt;
&lt;p&gt;You will also need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node.js&lt;/a&gt; version 22&lt;/li&gt;
&lt;li&gt;A &lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt; account&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting started with the API&lt;/h2&gt;
&lt;p&gt;Let&apos;s write a quick script to post a status to Bluesky from JavaScript. In your terminal &lt;a href=&quot;/blog/2019/01/10/how-to-start-a-node-js-project/&quot;&gt;create a new Node.js project&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir bluesky-bot
cd bluesky-bot
npm init --yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add an &lt;code&gt;.nvmrc&lt;/code&gt; file with the version 18.16.0 so that we can guarantee the Node.js version this will run on.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo 22 &amp;gt; .nvmrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the &lt;a href=&quot;https://www.npmjs.com/package/@atproto/api&quot;&gt;AT Protocol/Bluesky API client&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @atproto/api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This library gives us easy access to the Bluesky API, a rich text library for formatting links and mentions, and lower level access to the AT Protocol itself.&lt;/p&gt;
&lt;h3&gt;Send your first Bluesky post&lt;/h3&gt;
&lt;p&gt;Create a file called &lt;code&gt;index.js&lt;/code&gt; and open it in your editor.&lt;/p&gt;
&lt;p&gt;Start by requiring the &lt;code&gt;AtpAgent&lt;/code&gt; class from the package.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { AtpAgent } = require(&quot;@atproto/api&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create an asynchronous function that will connect to the service and send a post. Within that function instantiate a new agent, setting the service to &lt;code&gt;&quot;https://bsky.social&quot;&lt;/code&gt;, the only available AT Protocol service at the moment. Then log the agent in with your Bluesky identifier (your username) and an &lt;a href=&quot;https://blueskyfeeds.com/en/faq-app-password&quot;&gt;app password&lt;/a&gt;. This is just a quick script to get going with, so we&apos;re just going to embed our credentials for now, in reality you want to keep credentials out of your code and load them through environment variables or similar.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function sendPost() {
  const agent = new AtpAgent({ service: &quot;https://bsky.social&quot; });
  await agent.login({
    identifier: &quot;YOUR_IDENTIFIER_HERE&quot;,
    password: &quot;YOUR_PASSWORD_HERE&quot;,
  });
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you&apos;ve logged in, you can then use the agent to post a status.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function sendPost(text) {
  const agent = new AtpAgent({ service: &quot;https://bsky.social&quot; });
  await agent.login({
    identifier: &quot;YOUR_IDENTIFIER_HERE&quot;,
    password: &quot;YOUR_PASSWORD_HERE&quot;,
  });
  await agent.post({ text });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can call the &lt;code&gt;sendPost&lt;/code&gt; method with the text you want to send to the API:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sendPost(&quot;Hello from the Bluesky API!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run this code in your terminal with &lt;code&gt;node index.js&lt;/code&gt; and you will see your first post from the API on your Bluesky account.&lt;/p&gt;
&lt;h3&gt;Sending posts with rich text&lt;/h3&gt;
&lt;p&gt;If you want to send links or mentions on the platform you can&apos;t just send plain text. Instead you need to send rich text, and the library provides a function to create that. Let&apos;s update the above code to generate rich text and use it to make a post.&lt;/p&gt;
&lt;p&gt;First, require the &lt;code&gt;RichText&lt;/code&gt; module.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const { AtpAgent, RichText } = require(&quot;@atproto/api&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then take the text you want to send and create a new &lt;code&gt;RichText&lt;/code&gt; object with it. Use that object to detect the facets in the text, then pass both the text and the facets to the &lt;code&gt;post&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function sendPost(text) {
  const agent = new AtpAgent({ service: &quot;https://bsky.social&quot; });
  await agent.login({
    identifier: &quot;YOUR_IDENTIFIER_HERE&quot;,
    password: &quot;YOUR_PASSWORD_HERE&quot;,
  });
  const richText = new RichText({ text });
  await richText.detectFacets(agent);
  await agent.post({
    text: richText.text,
    facets: richText.facets,
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you call the &lt;code&gt;sendPost&lt;/code&gt; function with text that includes a user mention or a link, it will be correctly linked in Bluesky and notify the mentioned user.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sendPost(&quot;Hello from the Bluesky API! Hi @philna.sh!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s the basics on creating posts using the Bluesky API. Now let&apos;s take a look at scheduling the posts.&lt;/p&gt;
&lt;h2&gt;Scheduling with GitHub Actions&lt;/h2&gt;
&lt;p&gt;GitHub Actions lets you automate things in your repositories. This means we can use it to automate posting to Bluesky. One of the triggers for a GitHub Action is the &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule&quot;&gt;schedule&lt;/a&gt; which lets you run a workflow at specified times using cron syntax.&lt;/p&gt;
&lt;p&gt;We can add a GitHub Actions workflow to this application that will start working when we push the repo up to GitHub. Before we do that, we should remove our credentials first. Update the code that logs in to the Bluesky service to use environment variables instead of hard coding the credentials:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  await agent.login({
    identifier: process.env.BSKY_HANDLE,
    password: process.env.BSKY_PASSWORD,
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a directory called &lt;code&gt;.github&lt;/code&gt; with a directory called &lt;code&gt;workflows&lt;/code&gt; inside.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p .github/workflows
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a YAML file called &lt;code&gt;post.yml&lt;/code&gt; and open it in your editor. Add the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: &quot;Post to Bluesky&quot;

on: workflow_dispatch

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: &quot;.nvmrc&quot;
      - run: npm ci
      - name: Send post
        run: node index.js
        env:
          BSKY_HANDLE: ${{ secrets.BSKY_HANDLE }}
          BSKY_PASSWORD: ${{ secrets.BSKY_PASSWORD }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This workflow file does a bunch of things. It sets up one job called post that will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;run on the latest Ubuntu&lt;/li&gt;
&lt;li&gt;checkout the repository&lt;/li&gt;
&lt;li&gt;install the Node.js version listed in the &lt;code&gt;.nvmrc&lt;/code&gt; file we created earlier&lt;/li&gt;
&lt;li&gt;install the dependencies&lt;/li&gt;
&lt;li&gt;run the &lt;code&gt;index.js&lt;/code&gt; file, with two secrets added to the environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the workflow above the &lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch&quot;&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; trigger&lt;/a&gt; is present instead of the &lt;code&gt;schedule&lt;/code&gt; trigger. The &lt;code&gt;workflow_dispatch&lt;/code&gt; trigger allows you to start a workflow by visiting it in the GitHub UI and pressing a button. It&apos;s a great way to test your workflow is working without having to wait for the schedule or push a new commit.&lt;/p&gt;
&lt;p&gt;Create a GitHub repo, and push all this code up to it. In the repo settings, find &lt;em&gt;Actions secrets and variables&lt;/em&gt;. Add two secrets called &lt;code&gt;BSKY_HANDLE&lt;/code&gt; and &lt;code&gt;BSKY_PASSWORD&lt;/code&gt; which contain the credentials you were using earlier.&lt;/p&gt;
&lt;h3&gt;Testing it out&lt;/h3&gt;
&lt;p&gt;With your code and secrets in place head to the &lt;em&gt;Actions&lt;/em&gt; tab for your repo. Click on the workflow called &quot;Post to Bluesky&quot; and then find the button that says &quot;Run workflow&quot;. This is the &lt;code&gt;workflow_dispatch&lt;/code&gt; trigger and it will run the workflow, eventually running your code and posting to Bluesky. Use this to test out the workflow and any changes to the code before you eventually write the schedule.&lt;/p&gt;
&lt;h3&gt;Scheduling the workflow&lt;/h3&gt;
&lt;p&gt;Once you are happy with your code and that the workflow is working it&apos;s time to set up a schedule. Remove the &lt;code&gt;workflow_dispatch&lt;/code&gt; trigger and replace it with the &lt;code&gt;schedule&lt;/code&gt; trigger, which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on:
  schedule:
    - cron: &quot;30 5,17 * * *&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I don&apos;t read cron, but &lt;a href=&quot;https://crontab.guru/#30_5,17_*_*_*&quot;&gt;crontab.guru&lt;/a&gt; tells me that this would run the workflow at 5:30 and 17:30 every day. I recommend playing around with that tool to get your schedule correct.&lt;/p&gt;
&lt;p&gt;Once you are happy, save, commit and push to GitHub and your Bluesky bot will set off posting.&lt;/p&gt;
&lt;h2&gt;A template to make this easier&lt;/h2&gt;
&lt;p&gt;To make this easier, I &lt;a href=&quot;https://github.com/philnash/bsky-bot&quot;&gt;created a template&lt;/a&gt; that has all of the above ready to go for you. Hit the big green &quot;Use this template&quot; button on the repo and you will get your own project ready to go. All you need to do is &lt;a href=&quot;https://github.com/philnash/bsky-bot/blob/main/src/lib/getPostText.ts&quot;&gt;provide your own function&lt;/a&gt; that will return the text that will get posted to Bluesky. There are also instructions in the &lt;a href=&quot;https://github.com/philnash/bsky-bot#readme&quot;&gt;README&lt;/a&gt; to walk you through it all.&lt;/p&gt;
&lt;h2&gt;My first bot&lt;/h2&gt;
&lt;p&gt;I&apos;ve used this template repo to create my first bot on the Bluesky platform. It&apos;s a simple but fun one. It &lt;a href=&quot;https://staging.bsky.app/profile/dadjokes.skybot.club&quot;&gt;posts an hourly dad joke to Bluesky&lt;/a&gt; from the &lt;a href=&quot;https://icanhazdadjoke.com/&quot;&gt;icanhazdadjoke.com API&lt;/a&gt;. You can find &lt;a href=&quot;https://github.com/philnash/phils-bsky-bots&quot;&gt;the code for this bot on GitHub&lt;/a&gt; too.&lt;/p&gt;
&lt;h2&gt;Bluesky is going to be a lot of fun&lt;/h2&gt;
&lt;p&gt;When Twitter first started the availability of the API caused a wave of creativity from developers. Even though Bluesky remains in very early invite only mode there is already a &lt;a href=&quot;https://github.com/beeman/awesome-atproto&quot;&gt;lot of things being built&lt;/a&gt; and it is exciting to see.&lt;/p&gt;
&lt;p&gt;I&apos;m looking forward to creating bots with this method, but also exploring more of the API, data and protocol to see what can be achieved.&lt;/p&gt;
&lt;p&gt;If you have an account with Bluesky, &lt;a href=&quot;https://staging.bsky.app/profile/philna.sh&quot;&gt;come follow me here&lt;/a&gt;. See you in the sky!&lt;/p&gt;
</description><pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate><category>bluesky</category><category>atproto</category><category>typescript</category><category>javascript</category><category>node</category></item><item><title>Create a CLI Chatbot with the ChatGPT API and Node.js</title><link>https://philna.sh/blog/2023/03/13/create-a-cli-chatbot-with-chatgpt-api-and-node-js/</link><guid isPermaLink="true">https://philna.sh/blog/2023/03/13/create-a-cli-chatbot-with-chatgpt-api-and-node-js/</guid><description>&lt;p&gt;ChatGPT has taken the world by storm and this week &lt;a href=&quot;https://openai.com/blog/introducing-chatgpt-and-whisper-apis&quot;&gt;OpenAI released the ChatGPT API&lt;/a&gt;. I&apos;ve spent some time playing with &lt;a href=&quot;https://chat.openai.com/chat&quot;&gt;ChatGPT in the browser&lt;/a&gt;, but the best way to really get on board with these new capabilities is to try building something with it. With the API available, now is that time.&lt;/p&gt;
&lt;p&gt;This was inspired by &lt;a href=&quot;https://www.haihai.ai/chatgpt-api/&quot;&gt;Greg Baugues&apos;s implementation of a chatbot command line interface (CLI) in 16 lines of Python&lt;/a&gt;. I thought I&apos;d start by trying to build the same chatbot but using JavaScript.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(It turns out that &lt;a href=&quot;https://www.haihai.ai/chatgpt-api-js/&quot;&gt;Ricky Robinett also had this idea and published his bot code here&lt;/a&gt;, it&apos;s pleasing to see how similar the implementations are!)&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The code&lt;/h2&gt;
&lt;p&gt;It turns out that Node.js requires a bit more code to deal with command line input than Python, so where Greg&apos;s version was 16 lines mine takes 31. Having built this little bot, I&apos;m no less excited about the potential for building with this API though.&lt;/p&gt;
&lt;p&gt;Here&apos;s the full code, I&apos;ll explain what it is doing further down.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createInterface } from &quot;node:readline/promises&quot;;
import { stdin as input, stdout as output, env } from &quot;node:process&quot;;
import { Configuration, OpenAIApi } from &quot;openai&quot;;

const configuration = new Configuration({ apiKey: env.OPENAI_API_KEY });
const openai = new OpenAIApi(configuration);
const readline = createInterface({ input, output });

const chatbotType = await readline.question(
  &quot;What type of chatbot would you like to create? &quot;
);
const messages = [{ role: &quot;system&quot;, content: chatbotType }];
let userInput = await readline.question(&quot;Say hello to your new assistant.\n\n&quot;);

while (userInput !== &quot;.exit&quot;) {
  messages.push({ role: &quot;user&quot;, content: userInput });
  try {
    const response = await openai.createChatCompletion({
      messages,
      model: &quot;gpt-3.5-turbo&quot;,
    });

    const botMessage = response.data.choices[0].message;
    if (botMessage) {
      messages.push(botMessage);
      userInput = await readline.question(&quot;\n&quot; + botMessage.content + &quot;\n\n&quot;);
    } else {
      userInput = await readline.question(&quot;\nNo response, try asking again\n&quot;);
    }
  } catch (error) {
    console.log(error.message);
    userInput = await readline.question(&quot;\nSomething went wrong, try asking again\n&quot;);
  }
}

readline.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you run this code it looks like this:&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-left&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/chatgpt/chatgpt.gif&quot; alt=&quot;An example of the chatbot running. I ask it to respond in haiku and it does twice.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s dig into how it works and how you can build your own.&lt;/p&gt;
&lt;h2&gt;Building a chatbot&lt;/h2&gt;
&lt;p&gt;You will need an &lt;a href=&quot;https://platform.openai.com/overview&quot;&gt;OpenAI platform account&lt;/a&gt; to interact with the ChatGPT API. Once you have signed up, &lt;a href=&quot;https://platform.openai.com/account/api-keys&quot;&gt;create an API key&lt;/a&gt; from your account dashboard.&lt;/p&gt;
&lt;p&gt;As long as you have Node.js installed, the only other thing you&apos;ll need is the &lt;a href=&quot;https://www.npmjs.com/package/openai&quot;&gt;&lt;code&gt;openai&lt;/code&gt; Node.js module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let&apos;s start a Node.js project and create this CLI application. First create a directory for the project, change into it and initialise it with npm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir chatgpt-cli
cd chatgpt-cli
npm init --yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the &lt;code&gt;openai&lt;/code&gt; module as a dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install openai
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;package.json&lt;/code&gt; and add the key &lt;code&gt;&quot;type&quot;: &quot;module&quot;&lt;/code&gt; to the configuration, so we can build this as an ES module which will allow us to use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await&quot;&gt;top level await&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Create a file called &lt;code&gt;index.js&lt;/code&gt; and open it in your editor.&lt;/p&gt;
&lt;h3&gt;Interacting with the OpenAI API&lt;/h3&gt;
&lt;p&gt;There are two parts to the code, dealing with input and output on the command line and dealing with the OpenAI API. Let&apos;s start by looking at how the API works.&lt;/p&gt;
&lt;p&gt;First we import two objects from the &lt;code&gt;openai&lt;/code&gt; module, the &lt;code&gt;Configuration&lt;/code&gt; and &lt;code&gt;OpenAIApi&lt;/code&gt;. The &lt;code&gt;Configuration&lt;/code&gt; class will be used to create a configuration that holds the API key, you can then use that configuration to create an &lt;code&gt;OpenAIApi&lt;/code&gt; client.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { env } from &quot;node:process&quot;;
import { Configuration, OpenAIApi } from &quot;openai&quot;;

const configuration = new Configuration({ apiKey: env.OPENAI_API_KEY });
const openai = new OpenAIApi(configuration);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, we&apos;ll store the API key in the environment and read it with &lt;code&gt;env.OPENAI_API_KEY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To interact with the API we now use the OpenAI client to create chat completions for us. OpenAI&apos;s text generating models don&apos;t actually converse with you, but are built to take input and come up with plausible sounding text that would follow that input, &lt;a href=&quot;https://platform.openai.com/docs/guides/completion/introduction&quot;&gt;a completion&lt;/a&gt;. With ChatGPT, &lt;a href=&quot;https://platform.openai.com/docs/guides/chat/introduction&quot;&gt;the model is configured to receive a list of messages and then come up with a completion for the conversation&lt;/a&gt;. Messages in this system can come from one of 3 different entities, the &quot;system&quot;, &quot;user&quot; and &quot;assistant&quot;. The &quot;assistant&quot; is ChatGPT itself, the &quot;user&quot; is the person interacting and the system allows the program (or the user, as we&apos;ll see in this example) to provide instructions that define how the assistant behaves. Changing the system prompts for how the assistant behaves is one of the most interesting things to play around with and allows you to create different types of assistants.&lt;/p&gt;
&lt;p&gt;With our &lt;code&gt;openai&lt;/code&gt; object configured as above, we can create messages to send to an assistant and request a response like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const messages = [
  { role: &quot;system&quot;, content: &quot;You are a helpful assistant&quot; },
  { role: &quot;user&quot;, content: &quot;Can you suggest somewhere to eat in the centre of London?&quot; }
];
const response = await openai.createChatCompletion({
  messages,
  model: &quot;gpt-3.5-turbo&quot;,
});
console.log(response.data.choices[0].message);
// =&amp;gt; &quot;Of course! London is known for its diverse and delicious food scene...&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As the conversation goes on, we can add the user&apos;s questions and assistant&apos;s responses to the messages array, which we send with each request. That gives the bot history of the conversation, context for which it can build further answers on.&lt;/p&gt;
&lt;p&gt;To create the CLI, we just need to hook this up to user input in the terminal.&lt;/p&gt;
&lt;h3&gt;Interacting with the terminal&lt;/h3&gt;
&lt;p&gt;Node.js provides the &lt;a href=&quot;https://nodejs.org/api/readline.html&quot;&gt;Readline module&lt;/a&gt; which makes it easy to receive input and write output to streams. To work with the terminal, those streams will be &lt;code&gt;stdin&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can import &lt;code&gt;stdin&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt; from the &lt;code&gt;node:process&lt;/code&gt; module, renaming them to &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; to make them easier to use with Readline. We also import the &lt;code&gt;createInterface&lt;/code&gt; function from &lt;code&gt;node:readline&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createInterface } from &quot;node:readline/promises&quot;;
import { stdin as input, stdout as output } from &quot;node:process&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then pass the &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt; streams to &lt;code&gt;createInterface&lt;/code&gt; and that gives us an object we can use to write to the output and read from the input, all with the &lt;a href=&quot;https://nodejs.org/api/readline.html#rlquestionquery-options&quot;&gt;&lt;code&gt;question&lt;/code&gt; function&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const readline = createInterface({ input, output });

const chatbotType = await readline.question(
  &quot;What type of chatbot would you like to create? &quot;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above code hooks up the input and output stream. The &lt;code&gt;readline&lt;/code&gt; object is then used to post the question to the output and return a promise. When the user replies by writing into the terminal and pressing return, the promise resolves with the text that the user wrote.&lt;/p&gt;
&lt;h3&gt;Completing the CLI&lt;/h3&gt;
&lt;p&gt;With both of those parts, we can write all of the code. Create a new file called &lt;code&gt;index.js&lt;/code&gt; and enter the code below.&lt;/p&gt;
&lt;p&gt;We start with the imports we described above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createInterface } from &quot;node:readline/promises&quot;;
import { stdin as input, stdout as output, env } from &quot;node:process&quot;;
import { Configuration, OpenAIApi } from &quot;openai&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we initialise the API client and the Readline module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const configuration = new Configuration({ apiKey: env.OPENAI_API_KEY });
const openai = new OpenAIApi(configuration);
const readline = createInterface({ input, output });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we ask the first question of the user: &quot;What type of chatbot would you like to create?&quot;. We will use the answer of this to create a &quot;service&quot; message in a new array of messages that we will continue to add to as the conversation goes on.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const chatbotType = await readline.question(
  &quot;What type of chatbot would you like to create? &quot;
);
const messages = [{ role: &quot;system&quot;, content: chatbotType }];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then prompt the user to start interacting with the chatbot and start a loop that says while the user input is not equal to the string &quot;.exit&quot; keep sending that input to the API. If the user enters &quot;.exit&quot; the program will end, like in the Node.js REPL.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let userInput = await readline.question(&quot;Say hello to your new assistant.\n\n&quot;);

while (userInput !== &quot;.exit&quot;) {
  // loop
}

readline.close();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the loop we add the &lt;code&gt;userInput&lt;/code&gt; to the messages array as a &quot;user&quot; message. Then, within a try/catch block, send it to the OpenAI API. We set the model as &quot;gpt-3.5-turbo&quot; which is the underlying name for ChatGPT.&lt;/p&gt;
&lt;p&gt;When we get a response from the API we get the message out of the &lt;code&gt;response.data.choices&lt;/code&gt; array. If there is a message we store it as an &quot;assistant&quot; message in the array of messages and output it to the user, waiting for their input again using readline. If there is no message in the response from the API, we alert the user and wait for further user input. Finally, if there is an error making a request to the API we catch the error, log the message and tell the user to try again.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;while (userInput !== &quot;.exit&quot;) {
  messages.push({ role: &quot;user&quot;, content: userInput });
  try {
    const response = await openai.createChatCompletion({
      messages,
      model: &quot;gpt-3.5-turbo&quot;,
    });

    const botMessage = response.data.choices[0].message;
    if (botMessage) {
      messages.push(botMessage);
      userInput = await readline.question(&quot;\n&quot; + botMessage.content + &quot;\n\n&quot;);
    } else {
      userInput = await readline.question(&quot;\nNo response, try asking again\n&quot;);
    }
  } catch (error) {
    console.log(error.message);
    userInput = await readline.question(
      &quot;\nSomething went wrong, try asking again\n&quot;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put that all together and you have your assistant. The full code is at the top of this post or &lt;a href=&quot;https://github.com/philnash/asst/blob/basic-asst/index.js&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can now run the assistant by passing it your OpenAI API key as an environment on the command line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OPENAI_API_KEY=YOUR_API_KEY node index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start your interaction with the assistant, starting with it asking what kind of assistant you want. Once you&apos;ve declared that, you can start chatting with it.&lt;/p&gt;
&lt;h2&gt;Experimenting helps us to understand&lt;/h2&gt;
&lt;p&gt;Personally, I&apos;m not actually sure how useful ChatGPT is. It is clearly impressive, its ability to return text that reads as if it was written by a human is incredible. However, it returns content that is not necessarily correct, regardless of how confidently it presents that content.&lt;/p&gt;
&lt;p&gt;Experimenting with ChatGPT is the only way that we can try to understand what it useful for, thus building a simple chat bot likes this gives us grounds for that experiment. Learning that the system commands can give the bot different personalities and make it respond in different ways is very interesting.&lt;/p&gt;
&lt;p&gt;You might have heard, for example, that you can ask ChatGPT to help you with programming, but you could also specify a JSON structure and effectively use it as an API as well. But as you experiment with that you will likely find that it should not be an information API, but more likely something you can use to understand your natural text and turn it into a JSON object. To me this is exciting as it means that ChatGPT could help create more natural voice assistants, that can translate meaning from speech better than the existing crop that expect commands to be given in a more exact manner. I still have experimenting to do with this idea, and having this tool gives me that opportunity.&lt;/p&gt;
&lt;h2&gt;This is just the beginning&lt;/h2&gt;
&lt;p&gt;If experimenting with this technology is the important thing for us to understand what we can build with it and what we should or should not build with it, then making it easier to experiment is the next goal. My next goal is to expand this tool so that it can save, interact with and edit multiple assistants so that you can continue to work with them and improve them over time.&lt;/p&gt;
&lt;p&gt;In the meantime, you can check out &lt;a href=&quot;https://github.com/philnash/asst/tree/basic-asst&quot;&gt;the full code for this first assistant in GitHub&lt;/a&gt;, follow the repo to keep up with improvements.&lt;/p&gt;
</description><pubDate>Mon, 13 Mar 2023 00:00:00 GMT</pubDate><category>openai</category><category>chatgpt</category><category>javascript</category><category>node</category></item><item><title>The yaml document from hell — JavaScript edition</title><link>https://philna.sh/blog/2023/02/02/yaml-document-from-hell-javascript-edition/</link><guid isPermaLink="true">https://philna.sh/blog/2023/02/02/yaml-document-from-hell-javascript-edition/</guid><description>&lt;p&gt;I recently came across this &lt;a href=&quot;https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell&quot;&gt;blog post from Ruud van Asseldonk titled &quot;The yaml document from hell&quot;&lt;/a&gt;. I&apos;ve always heard that yaml has its pitfalls, but hadn&apos;t looked into the details and thankfully hadn&apos;t been affected, mainly due to my very infrequent and simple use of yaml. If you are in the same boat as me, I recommend reading &lt;a href=&quot;https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell&quot;&gt;that article&lt;/a&gt; now as I almost can&apos;t believe I&apos;ve avoided any issues with it.&lt;/p&gt;
&lt;p&gt;The article digs into the issues in the yaml spec itself, and then describes what happens in Python&apos;s &lt;a href=&quot;https://pypi.org/project/PyYAML/6.0/&quot;&gt;PyYAML&lt;/a&gt; and &lt;a href=&quot;https://github.com/go-yaml/yaml&quot;&gt;Golang&apos;s yaml library&lt;/a&gt; with an example file, the titular yaml document from hell. I wanted to see how things were in the JavaScript ecosystem.&lt;/p&gt;
&lt;h2&gt;Yaml in JavaScript&lt;/h2&gt;
&lt;p&gt;A search for JavaScript yaml parsers on npm brings up &lt;a href=&quot;https://github.com/eemeli/yaml/&quot;&gt;yaml&lt;/a&gt; (which I have used in &lt;a href=&quot;https://github.com/philnash/ngrok-for-vscode&quot;&gt;my own project&lt;/a&gt;) and &lt;a href=&quot;https://github.com/nodeca/js-yaml&quot;&gt;js-yaml&lt;/a&gt;. js-yaml has the &lt;a href=&quot;https://npmtrends.com/js-yaml-vs-yaml&quot;&gt;most weekly downloads according to npm and the most stars on GitHub&lt;/a&gt; however yaml seems to be under more active development, having been most recently published (a month ago at the time of writing) compared to js-yaml&apos;s last publish date almost 2 years ago. There is also &lt;a href=&quot;https://github.com/jeremyfa/yaml.js&quot;&gt;yamljs&lt;/a&gt;, but the project hasn&apos;t received a commit since November 2019 and hasn&apos;t been released for 6 years, so I am going to disregard it for now.&lt;/p&gt;
&lt;p&gt;Let&apos;s see what yaml and js-yaml do with the yaml document from hell.&lt;/p&gt;
&lt;h2&gt;The document itself&lt;/h2&gt;
&lt;p&gt;To save yourself from going back and forth between &lt;a href=&quot;https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell&quot;&gt;van Asseldonk&apos;s article&lt;/a&gt; and this one, here is the yaml document.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server_config:
  port_mapping:
    # Expose only ssh and http to the public internet.
    - 22:22
    - 80:80
    - 443:443

  serve:
    - /robots.txt
    - /favicon.ico
    - *.html
    - *.png
    - !.git  # Do not expose our Git repository to the entire world.

  geoblock_regions:
    # The legal team has not approved distribution in the Nordics yet.
    - dk
    - fi
    - is
    - no
    - se

  flush_cache:
    on: [push, memory_pressure]
    priority: background

  allow_postgres_versions:
    - 9.5.25
    - 9.6.24
    - 10.23
    - 12.13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So how do our JavaScript libraries handle this file?&lt;/p&gt;
&lt;h2&gt;The failures&lt;/h2&gt;
&lt;h3&gt;Anchors, aliases, and tags&lt;/h3&gt;
&lt;p&gt;Let&apos;s start with the failures. As described in &lt;a href=&quot;https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell#anchors-aliases-and-tags&quot;&gt;the original article under the subhead &quot;Anchors, aliases, and tags&quot;&lt;/a&gt; this section is invalid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  serve:
    - /robots.txt
    - /favicon.ico
    - *.html
    - *.png
    - !.git  # Do not expose our Git repository to the entire world.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This causes both of our JavaScript yaml libraries to throw an error, both referencing an undefined alias. This is because the &lt;code&gt;*&lt;/code&gt; is a way to reference an anchor created earlier in the document using an &lt;code&gt;&amp;amp;&lt;/code&gt;. In our document&apos;s case, that anchor was never created, so this is a parsing error.&lt;/p&gt;
&lt;p&gt;If you want to learn more about anchors and aliases it seems like something that is important in build pipelines. Both &lt;a href=&quot;https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/&quot;&gt;Bitbucket&lt;/a&gt; and &lt;a href=&quot;https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html&quot;&gt;GitLab&lt;/a&gt; have written about how to use anchors to avoid repeating sections in yaml files.&lt;/p&gt;
&lt;p&gt;For the purposes of trying to get the file to parse, we can make those aliases strings as they were likely intended.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  serve:
    - /robots.txt
    - /favicon.ico
    - &quot;*.html&quot;
    - &quot;*.png&quot;
    - !.git  # Do not expose our Git repository to the entire world.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we get another parsing error from our libraries; both of them complain about an unknown or unresolved tag. The &lt;code&gt;!&lt;/code&gt; at the start of &lt;code&gt;!.git&lt;/code&gt; is the character triggering this behaviour.&lt;/p&gt;
&lt;p&gt;Tags seem to be the most complicated part of yaml to me. They depend on the parser you are using and allow that parser to do something custom with the content that follows the tag. My understanding is that you could use this in JavaScript to, say, tag some content to be parsed into a &lt;code&gt;Map&lt;/code&gt; instead of an &lt;code&gt;Object&lt;/code&gt; or a &lt;code&gt;Set&lt;/code&gt; instead of an &lt;code&gt;Array&lt;/code&gt;. Van Asseldonk explains this with this alarming sentence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This means that &lt;strong&gt;loading an untrusted yaml document is generally unsafe&lt;/strong&gt;, as it may lead to arbitrary code execution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PyYaml apparently has a &lt;code&gt;safe_load&lt;/code&gt; method that will avoid this, but Go&apos;s yaml package doesn&apos;t. It seems that the JavaScript libraries also lack this feature, so the warning for untrusted yaml documents stands.&lt;/p&gt;
&lt;p&gt;If you do want to take advantage of the tag feature in yaml, you can check out the &lt;a href=&quot;https://eemeli.org/yaml/#custom-data-types&quot;&gt;yaml package&apos;s documentation on custom data types&lt;/a&gt; or &lt;a href=&quot;https://github.com/nodeca/js-yaml#supported-yaml-types&quot;&gt;js-yaml&apos;s supported yaml types&lt;/a&gt; and &lt;a href=&quot;https://github.com/nodeca/js-yaml-js-types&quot;&gt;unsafe type extensions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make the yaml file parse, let&apos;s encase all the weird yaml artifacts in quotes to make them strings:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  serve:
    - /robots.txt
    - /favicon.ico
    - &quot;*.html&quot;
    - &quot;*.png&quot;
    - &quot;!.git&quot;  # Do not expose our Git repository to the entire world.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;serve&lt;/code&gt; block looking it does above, the file now parses. So what happens to the rest of the potential yaml gotchas?&lt;/p&gt;
&lt;h3&gt;Accidental numbers&lt;/h3&gt;
&lt;p&gt;One thing that I am gathering from this investigation so far is that if you need something to be a string, do not be ambiguous about it, surround it in quotes. That counted for the aliases and tags above and it also counts for accidental numbers. In the following section of the yaml file you see a list of version numbers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  allow_postgres_versions:
    - 9.5.25
    - 9.6.24
    - 10.23
    - 12.13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Version numbers are strings, numbers can&apos;t have more than one decimal point in them. But when this is parsed by either JavaScript library the result is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  allow_postgres_versions: [ &apos;9.5.25&apos;, &apos;9.6.24&apos;, 10.23, 12.13 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have an array of strings and numbers. If a yaml parser thinks something looks like a number it will parse it as such. And when you come to use those values they might not act as you expect.&lt;/p&gt;
&lt;h4&gt;Version numbers in GitHub Actions&lt;/h4&gt;
&lt;p&gt;I have had this issue within GitHub Actions before. It was &lt;a href=&quot;https://github.com/philnash/bitly/blob/main/.github/workflows/tests.yml#L11&quot;&gt;in a Ruby project&lt;/a&gt;, but this applies to anyone trying to use version numbers in a GitHub Actions yaml file. I tried to use a list of Ruby version numbers, this worked fine up until Ruby version 3.1 was released. I had &lt;code&gt;3.0&lt;/code&gt; in the array. Within GitHub Actions this was parsed as the integer &lt;code&gt;3&lt;/code&gt;. This might seem fine, except that when you give an integer version to GitHub Actions it picks the latest minor point for that version. So, once Ruby 3.1 was released, the number &lt;code&gt;3.0&lt;/code&gt; would select version 3.1. I had to make the version number a string, &lt;code&gt;&quot;3.0&quot;&lt;/code&gt;, and then it was applied correctly.&lt;/p&gt;
&lt;p&gt;Accidental numbers cause issues. If you need a string, make sure you provide a string.&lt;/p&gt;
&lt;h2&gt;The successes&lt;/h2&gt;
&lt;p&gt;It&apos;s not all bad in the JavaScript world. After working through the issues above, we might now be in the clear. Let&apos;s take a look now at what parsed correctly from this yaml file.&lt;/p&gt;
&lt;h3&gt;Sexagesimal numbers&lt;/h3&gt;
&lt;p&gt;Under the port mapping section of the yaml file we see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  port_mapping:
    # Expose only ssh and http to the public internet.
    - 22:22
    - 80:80
    - 443:443
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;22:22&lt;/code&gt; is dangerous in yaml version 1.1 and PyYaml parses it as a sexagesimal (base 60) number, giving the result of &lt;code&gt;1342&lt;/code&gt;. Thankfully both JavaScript libraries have implemented yaml 1.2 and &lt;code&gt;22:22&lt;/code&gt; is parsed correctly as a string in this case.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  port_mapping: [ &apos;22:22&apos;, &apos;80:80&apos;, &apos;443:443&apos; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The Norway problem&lt;/h3&gt;
&lt;p&gt;In yaml 1.1 &lt;code&gt;no&lt;/code&gt; is parsed as &lt;code&gt;false&lt;/code&gt;. This is known as &quot;the Norway problem&quot; because listing countries as two character identifiers is fairly common and having this yaml:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  geoblock_regions:
    - dk
    - fi
    - is
    - no
    - se
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;parsed into this JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  geoblock_regions: [ &apos;dk&apos;, &apos;fi&apos;, &apos;is&apos;, false, &apos;se&apos; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is just not helpful. The good news is that, &lt;a href=&quot;https://github.com/go-yaml/yaml/tree/v3.0.1#compatibility&quot;&gt;unlike Go&apos;s yaml library&lt;/a&gt;, both JavaScript libraries have implemented yaml 1.2 and dropped &lt;code&gt;no&lt;/code&gt; as an alternative for &lt;code&gt;false&lt;/code&gt;. The &lt;code&gt;geoblock_regions&lt;/code&gt; sections is successfully parsed as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  geoblock_regions: [ &apos;dk&apos;, &apos;fi&apos;, &apos;is&apos;, &apos;no&apos;, &apos;se&apos; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Non-string keys&lt;/h3&gt;
&lt;p&gt;You might believe that keys in yaml would be parsed as strings, like JSON. However they can be any value. Once again there are values that may trip you up. Much like with the Norway problem in which &lt;code&gt;yes&lt;/code&gt; and &lt;code&gt;no&lt;/code&gt; can be parsed as &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt;, the same goes for &lt;code&gt;on&lt;/code&gt; and &lt;code&gt;off&lt;/code&gt;. This is manifested in our yaml file in the &lt;code&gt;flush_cache&lt;/code&gt; section:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  flush_cache:
    on: [push, memory_pressure]
    priority: background
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the key is &lt;code&gt;on&lt;/code&gt;, but in some libraries it is parsed as a boolean. In Python, even more confusingly the boolean is then stringified and appears as the key &lt;code&gt;&quot;True&quot;&lt;/code&gt;. Thankfully this is handled by the JavaScript libraries and &lt;code&gt;on&lt;/code&gt; becomes the key &lt;code&gt;&quot;on&quot;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  flush_cache: { on: [ &apos;push&apos;, &apos;memory_pressure&apos; ], priority: &apos;background&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is of particular concern in GitHub Actions again, where &lt;code&gt;on&lt;/code&gt; is used to determine what events should trigger an Action. I wonder if GitHub had to work around this when implementing their parsing.&lt;/p&gt;
&lt;h2&gt;Parsing as yaml version 1.1&lt;/h2&gt;
&lt;p&gt;Many of the issues that our JavaScript libraries sidestep are problems from yaml 1.1 and both libraries have fully implemented yaml 1.2. If you do wish to throw caution to the wind, or you have to parse a yaml file explicitly with yaml 1.1 settings, the &lt;a href=&quot;https://github.com/eemeli/yaml/&quot;&gt;yaml&lt;/a&gt; library can do that for you. You can pass a second argument to the &lt;code&gt;parse&lt;/code&gt; function to tell it to use version 1.1, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { parse } from &quot;yaml&quot;;
const yaml = parse(yamlContents, { version: &quot;1.1&quot; });
console.log(yaml);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you get a result with all of the fun described above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  server_config: {
    port_mapping: [ 1342, &apos;80:80&apos;, &apos;443:443&apos; ],
    serve: [ &apos;/robots.txt&apos;, &apos;/favicon.ico&apos;, &apos;*.html&apos;, &apos;*.png&apos;, &apos;!.git&apos; ],
    geoblock_regions: [ &apos;dk&apos;, &apos;fi&apos;, &apos;is&apos;, false, &apos;se&apos; ],
    flush_cache: { true: [ &apos;push&apos;, &apos;memory_pressure&apos; ], priority: &apos;background&apos; },
    allow_postgres_versions: [ &apos;9.5.25&apos;, &apos;9.6.24&apos;, 10.23, 12.13 ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that in this case I left the aliases and tags quoted as strings so that the file could be parsed successfully.&lt;/p&gt;
&lt;p&gt;Stick with version 1.2, the default in both JavaScript yaml libraries, and you&apos;ll get a much more sensible result.&lt;/p&gt;
&lt;h2&gt;Isn&apos;t yaml fun?&lt;/h2&gt;
&lt;p&gt;In this post we&apos;ve seen that it&apos;s easy to write malformed yaml if you weren&apos;t aware of aliases or tags. It&apos;s also easy to write mixed arrays of strings and numbers. There are also languages and libraries in which yaml 1.1 is still hanging around and &lt;code&gt;on&lt;/code&gt;. &lt;code&gt;yes&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt;, and &lt;code&gt;no&lt;/code&gt; are booleans and some numbers can be parsed into base 60.&lt;/p&gt;
&lt;p&gt;My advice, after going through all of this, is to err on the side of caution when writing yaml. If you want a key or a value to be a string, surround it in quotes and explicitly make it a string.&lt;/p&gt;
&lt;p&gt;On the other hand, if you are parsing someone else&apos;s yaml then you will need to program defensively and try to handle the edge cases, like accidental numbers, that can still cause issues.&lt;/p&gt;
&lt;p&gt;Finally, if you have the option, choose a different format to yaml. Yaml is supposed to be human-friendly, but the surprises and the bugs that it can produce are certainly not developer-friendly and ultimately that defeats the purpose.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell#alternative-configuration-formats&quot;&gt;conclusion to the original yaml document from hell post suggests many alternatives to yaml&lt;/a&gt; that will work better. I can&apos;t help but think that in the world of JavaScript that something JSON based, but  friendlier to author, should be the solution.&lt;/p&gt;
&lt;p&gt;There is a package that simply &lt;a href=&quot;https://www.npmjs.com/package/strip-json-comments&quot;&gt;strips comments from JSON&lt;/a&gt; or there&apos;s &lt;a href=&quot;https://www.npmjs.com/package/json5&quot;&gt;JSON5&lt;/a&gt; a JSON format that aims to be easier to write and maintain by hand. JSON5 supports comments as well as trailing commas, multiline strings, and various number formats. Either of these are a good start if you want to make authoring JSON easier and parsing hand authored files more consistent.&lt;/p&gt;
&lt;p&gt;If you can avoid yaml, I recommend it. If you can&apos;t, good luck.&lt;/p&gt;
</description><pubDate>Thu, 02 Feb 2023 00:00:00 GMT</pubDate><category>javascript</category><category>yaml</category></item><item><title>Better two factor authentication experiences with WebOTP</title><link>https://philna.sh/blog/2022/12/07/better-two-factor-authentication-experiences-with-web-otp/</link><guid isPermaLink="true">https://philna.sh/blog/2022/12/07/better-two-factor-authentication-experiences-with-web-otp/</guid><description>&lt;p&gt;Two factor authentication (2FA) is a great way to improve the security of user accounts in an application. It helps protect against common issues with passwords, like users picking easily guessable passwords or reusing the same password across multiple sites. There are different ways to implement two factor authentication, including SMS, using an authenticator application and WebAuthn.&lt;/p&gt;
&lt;p&gt;SMS is the most widely used and &lt;a href=&quot;https://www.twilio.com/blog/sms-2fa-security&quot;&gt;won&apos;t be going away&lt;/a&gt;, so it falls on us as developers to do our best to build the best SMS 2FA experience for our users. The WebOTP API is one way we can help reduce friction in the login experience and even provide some protection against phishing.&lt;/p&gt;
&lt;h2&gt;What is the WebOTP API?&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API&quot;&gt;WebOTP API&lt;/a&gt; is an extension to the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Credential_Management_API&quot;&gt;Credential Management API&lt;/a&gt;. The Credential Management API started by giving us the ability to store and access credentials in a browser&apos;s password manager, but now encompasses &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API&quot;&gt;WebAuthn&lt;/a&gt; and two factor authentication. The WebOTP API allows us to request permission from the user to read a 2FA code out of an incoming SMS message.&lt;/p&gt;
&lt;p&gt;When you implement the WebOTP API the second step of a login process can go from an awkward process of reading and copying a number of digits from an SMS, to a single button press. A great improvement, I think you&apos;ll agree.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;/posts/webotp/webotp.gif&quot; alt=&quot;An animation showing a login experience where after entering a username and password, a permissions dialog pops up asking for permission to read a 2FA code from an SMS. When approved, the code is entered into an input and the form submitted.&quot; loading=&quot;lazy&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;How does it work?&lt;/h2&gt;
&lt;p&gt;To implement WebOTP you will need to do two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update the message you send with the WebOTP format&lt;/li&gt;
&lt;li&gt;Add some JavaScript to the login page to request permission to read the message&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;The SMS message&lt;/h3&gt;
&lt;p&gt;To have the WebOTP API recognise a message as an incoming 2FA code you need to add a line to the end of the message that you send. That line must include an &lt;code&gt;@&lt;/code&gt; symbol followed by the domain for the site that your user will be logging in to, then a space, the &lt;code&gt;#&lt;/code&gt; symbol and then the code itself. If your user is logging in on &lt;code&gt;example.com&lt;/code&gt; and the code you are sending them is &lt;code&gt;123456&lt;/code&gt; then the message needs to look like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your code to log in to the application is 123456&lt;/p&gt;
&lt;p&gt;@example.com #123456&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The domain ties the message to the website the user should be logging onto. This helps protect against phishing, WebOTP can&apos;t be used to request the code from an SMS if the domain the user is logging in to doesn&apos;t match the domain in the message. Obviously it can&apos;t stop a user copying a code across from a message, but it might give them pause if they come to expect this behaviour.&lt;/p&gt;
&lt;h3&gt;The JavaScript&lt;/h3&gt;
&lt;p&gt;Once you have your messages set up in the right format you need some JavaScript on your 2nd factor page that will trigger the WebOTP API, ask the user permission for access to the message and collect the code.&lt;/p&gt;
&lt;p&gt;The most minimal version of this code looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (&apos;OTPCredential&apos; in window) {
  navigator.credentials.get({
    otp: {
      transport: [&apos;sms&apos;]
    }
  }).then((otp) =&amp;gt; {
    submitOTP(otp.code);
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We ask the &lt;code&gt;navigator.credentials&lt;/code&gt; object to get a one time password (OTP) from the SMS transport. If the browser detects an incoming message with the right domain and a code in it, the user will be prompted, asking for access. If the user approves the promise resolves with an &lt;code&gt;otp&lt;/code&gt; object which has a &lt;code&gt;code&lt;/code&gt; property. You can then submit that code to the form and complete the user&apos;s login process.&lt;/p&gt;
&lt;p&gt;A more complete version of the code, that handles things like finding an input and form, cancelling the request if the form is submitted, and submitting the form if the request is successful, looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (&apos;OTPCredential&apos; in window) {
  window.addEventListener(&apos;DOMContentLoaded&apos;, e =&amp;gt; {
    const input = document.querySelector(&apos;input[autocomplete=&quot;one-time-code&quot;]&apos;);
    if (!input) return;
    const ac = new AbortController();
    const form = input.closest(&apos;form&apos;);
    if (form) {
      form.addEventListener(&apos;submit&apos;, e =&amp;gt; ac.abort());
    }
    navigator.credentials.get({
      otp: { transport:[&apos;sms&apos;] },
      signal: ac.signal
    }).then(otp =&amp;gt; {
      input.value = otp.code;
      if (form) {
        form.submit();
      }
    }).catch(err =&amp;gt; {
      console.error(err);
    });
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will work for many sites, but copying and pasting code isn&apos;t the best way to share code, so I came up with something a bit easier.&lt;/p&gt;
&lt;h3&gt;Declarative WebOTP with web components&lt;/h3&gt;
&lt;p&gt;On Safari, you can get similar behaviour to the WebOTP API by adding one attribute to the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; element for the OTP code. Setting &lt;code&gt;autocomplete=&quot;one-time-code&quot;&lt;/code&gt; will trigger Safari to offer the code from the SMS via autocomplete.&lt;/p&gt;
&lt;p&gt;Inspired by this, I wanted to make WebOTP just as easy. So, I published a web component, the &lt;a href=&quot;https://github.com/philnash/web-otp-input&quot;&gt;&lt;code&gt;&amp;lt;web-otp-input&amp;gt;&lt;/code&gt; component&lt;/a&gt;, that handles the entire process. You can see &lt;a href=&quot;https://github.com/philnash/web-otp-input&quot;&gt;all the code and how to use it on GitHub&lt;/a&gt;. For a quick example, you can add the component to your page as an ES module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;module&quot; src=&quot;https://unpkg.com/@philnash/web-otp-input&quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or install it to your project from npm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @philnash/web-otp-input
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and import it to your application:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { WebOTPInput } from &quot;@philnash/web-otp-input&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then wrap the &lt;code&gt;&amp;lt;web-otp-input&amp;gt;&lt;/code&gt; around your existing &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; within a &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;form action=&quot;/verification&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;label for=&quot;otp&quot;&amp;gt;Enter your code:&amp;lt;/label&amp;gt;
    &amp;lt;web-otp-input&amp;gt;
      &amp;lt;input type=&quot;text&quot; autocomplete=&quot;one-time-code&quot; inputmode=&quot;numeric&quot; id=&quot;otp&quot; name=&quot;otp&quot; /&amp;gt;
    &amp;lt;/web-otp-input&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the WebOTP experience will happen automatically for anyone on a browser that supports it, without writing any additional JavaScript.&lt;/p&gt;
&lt;h2&gt;WebOTP: a better experience&lt;/h2&gt;
&lt;p&gt;The WebOTP API makes two factor authentication with SMS a better experience. &lt;a href=&quot;https://caniuse.com/mdn-api_otpcredential&quot;&gt;For browsers that support it&lt;/a&gt;, entering the code that is sent as a second factor becomes a breeze for users.&lt;/p&gt;
&lt;p&gt;There are even circumstances where it works for desktop browsers too. For a user with Chrome on the desktop and Chrome on Android and signed into their Google account on both, signing in on the desktop will cause a notification on the mobile device asking to approve sending the code to the desktop. Approving that on the mobile devices transfers the code to the desktop browser. You don&apos;t even have to write more code to handle this, all you need is the JavaScript in this article.&lt;/p&gt;
&lt;p&gt;For more on WebOTP, check out these articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/web-otp/&quot;&gt;Verify phone numbers on the web with the WebOTP API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.chrome.com/blog/cross-device-webotp/&quot;&gt;Verify a phone number on desktop using WebOTP API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are building two factor authentication or phone verification, consider implementing the WebOTP API as well to make that process easier for your users.&lt;/p&gt;
</description><pubDate>Wed, 07 Dec 2022 00:00:00 GMT</pubDate><category>web</category><category>javascript</category><category>security</category><category>user experience</category></item><item><title>Alias your Mastodon username to your own domain with Jekyll</title><link>https://philna.sh/blog/2022/11/23/alias-your-mastodon-username-to-your-own-domain-with-jekyll/</link><guid isPermaLink="true">https://philna.sh/blog/2022/11/23/alias-your-mastodon-username-to-your-own-domain-with-jekyll/</guid><description>&lt;p&gt;Mastodon is different to most online services. It is a federated network, so when you set up an account you need to &lt;a href=&quot;https://docs.joinmastodon.org/user/signup/&quot;&gt;choose a server to use&lt;/a&gt;. Your username then becomes a combination of your handle and that server you signed up to. For example, I am currently &lt;a href=&quot;https://mastodon.social/@philnash&quot;&gt;@philnash@mastodon.social&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But what if you want to personalise that a bit more? What if you wanted to use your own domain for your Mastodon account without having to host a whole Mastodon server? Using your own domain means that no matter what instance you used, or if you moved instance, you could share one Mastodon username that always pointed to the right profile and was personalised to your own site.&lt;/p&gt;
&lt;h2&gt;WebFinger to the rescue&lt;/h2&gt;
&lt;p&gt;It turns out that you can do this. &lt;a href=&quot;https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html&quot;&gt;Maarten Balliauw wrote about how Mastodon uses WebFinger to attach extra information to an email address&lt;/a&gt;. Information like an associated profile page or &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;ActivityPub&lt;/a&gt; stream.&lt;/p&gt;
&lt;p&gt;Implementing WebFinger requires your domain to respond to a request to &lt;code&gt;/.well-known/webfinger&lt;/code&gt; with a JSON representation of the associated accounts. If you have a Mastodon account you can check out what your WebFinger JSON looks like by making a request to &lt;code&gt;https://#{instance}/.well-known/webfinger?resource=acct:#{username}@#{instance}&lt;/code&gt;. For example, my WebFinger JSON is available at this URL: &lt;a href=&quot;https://mastodon.social/.well-known/webfinger?resource=acct:philnash@mastodon.social&quot;&gt;https://mastodon.social/.well-known/webfinger?resource=acct:philnash@mastodon.social&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To associate a Mastodon account with your own domain, you can serve this JSON yourself from a &lt;code&gt;/.well-known/webfinger&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;h2&gt;WebFinger with Jekyll&lt;/h2&gt;
&lt;p&gt;As Maarten pointed out in his post, you can copy the JSON response from your Mastodon instance to a file that you then serve from your own site. My site is powered by &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, so I wanted to make it easy for me, and anyone else using Jekyll, to create and serve that WebFinger JSON. I&apos;ve also built Jekyll plugins before, like &lt;a href=&quot;https://github.com/philnash/jekyll-gzip&quot;&gt;jekyll-gzip&lt;/a&gt;, &lt;a href=&quot;https://github.com/philnash/jekyll-brotli&quot;&gt;jekyll-brotli&lt;/a&gt;, &lt;a href=&quot;https://github.com/philnash/jekyll-zopfli&quot;&gt;jekyll-zopfli&lt;/a&gt;, and &lt;a href=&quot;https://github.com/philnash/jekyll-web_monetization&quot;&gt;jekyll-web_monetization&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I got to work and built &lt;a href=&quot;https://github.com/philnash/jekyll-mastodon_webfinger&quot;&gt;jekyll-mastodon_webfinger&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;How to use it&lt;/h3&gt;
&lt;p&gt;You can serve up your own WebFinger JSON on your Jekyll site to point to your Mastodon profile by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;jekyll-mastodon_webfinger&lt;/code&gt; to your Gemfile:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle add jekyll-mastodon_webfinger
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the plugin to your list of plugins in &lt;code&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugins:
  - jekyll/mastodon_webfinger
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add your Mastodon username and instance to &lt;code&gt;_config.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mastodon:
  username: philnash
  instance: mastodon.social
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next time you build the site, you will find a &lt;code&gt;/.well-known/webfinger&lt;/code&gt; file in your output directory, and when you deploy you will be able to refer to your Mastodon account using your own domain.&lt;/p&gt;
&lt;p&gt;You can see the result of this by checking the WebFinger endpoint on my domain: &lt;a href=&quot;https://philna.sh/.well-known/webfinger&quot;&gt;https://philna.sh/.well-known/webfinger&lt;/a&gt; or by searching for &lt;code&gt;@phil@philna.sh&lt;/code&gt; on your Mastodon instance.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/mastodon/search.png&quot; alt=&quot;When you search for @phil@philna.sh on your Mastodon instance, you will find my account&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;As this is a static file it sort of acts like a catch-all email address. You can actually search for &lt;code&gt;@any_username@philna.sh&lt;/code&gt; and you will find me. If you wanted to restrict this, you would need to build an endpoint that could respond dynamically to the request.&lt;/p&gt;
&lt;h2&gt;Other ways to serve Mastodon WebFinger responses&lt;/h2&gt;
&lt;p&gt;I&apos;m not the only one to have considered this. Along with &lt;a href=&quot;https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html&quot;&gt;Maarten&apos;s original post on the topic&lt;/a&gt;, others have built tools or posted about how to do this with your own site.&lt;/p&gt;
&lt;p&gt;Lindsay Wardell wrote up &lt;a href=&quot;https://www.lindsaykwardell.com/blog/integrate-mastodon-with-astro&quot;&gt;how to integrate Mastodon with Astro&lt;/a&gt; including showing how to display her feed within her Astro site.&lt;/p&gt;
&lt;p&gt;Dominik Kundel put together a &lt;a href=&quot;https://github.com/dkundel/netlify-plugin-mastodon-alias&quot;&gt;Netlify plugin that generates a Mastodon WebFinger&lt;/a&gt; file for your Netlify hosted site.&lt;/p&gt;
&lt;h2&gt;Take a trip into the Fediverse&lt;/h2&gt;
&lt;p&gt;An interesting side-effect of the increase in popularity of Mastodon is learning and understanding the protocols that underpin federating a social network like this. &lt;a href=&quot;http://webfinger.net/&quot;&gt;WebFinger&lt;/a&gt; and &lt;a href=&quot;https://activitypub.rocks/&quot;&gt;ActivityPub&lt;/a&gt; are having their moment and I look forward to see what further integrations and applications can be built on top of them.&lt;/p&gt;
&lt;p&gt;In the meantime, you can use the techniques in this post to use your own domain as an alias for your Mastodon profile. And if you fancy it, connect with me on Mastodon by searching for &lt;code&gt;@phil@philna.sh&lt;/code&gt; or at &lt;a href=&quot;https://mastodon.social/@philnash&quot;&gt;https://mastodon.social/@philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 23 Nov 2022 00:00:00 GMT</pubDate><category>ruby</category><category>jekyll</category><category>mastodon</category></item><item><title>How to use the Bitly API in Ruby</title><link>https://philna.sh/blog/2022/11/09/how-to-use-the-bitly-api-in-ruby/</link><guid isPermaLink="true">https://philna.sh/blog/2022/11/09/how-to-use-the-bitly-api-in-ruby/</guid><description>&lt;p&gt;Link shortening has been around for a long time and &lt;a href=&quot;https://bitly.com/&quot;&gt;Bitly&lt;/a&gt; is arguably the king of the link shorteners. It has support for shortening long URLs as well as custom short links, custom domains, and metrics to track how each link is performing.&lt;/p&gt;
&lt;p&gt;For those of us with the power of code at our fingertips, &lt;a href=&quot;https://dev.bitly.com/&quot;&gt;Bitly also has an API&lt;/a&gt;. With the Bitly API you can build all of the functionality of Bitly into your own applications and expose it to your users. In this post you&apos;ll learn how to use the &lt;a href=&quot;https://rubygems.org/gems/bitly&quot;&gt;Bitly Ruby gem&lt;/a&gt; to use the Bitly API in your Ruby applications.&lt;/p&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;To start shortening or expanding links with the Bitly gem, you&apos;ll need &lt;a href=&quot;https://www.ruby-lang.org/en/downloads/&quot;&gt;Ruby installed&lt;/a&gt; and a &lt;a href=&quot;https://bitly.com/pages/pricing&quot;&gt;Bitly account&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make API requests against the Bitly API you will need an access token. Log in to your Bitly account and head to the &lt;a href=&quot;https://app.bitly.com/settings/api/&quot;&gt;API settings&lt;/a&gt;. Here you can enter your account password and generate a new token. This token will only be shown once, so copy it now.&lt;/p&gt;
&lt;h2&gt;Using the Bitly API&lt;/h2&gt;
&lt;p&gt;Open a terminal and install the Bitly gem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem install bitly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s explore the gem in the terminal. Open an &lt;code&gt;irb&lt;/code&gt; session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;irb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Require the gem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;bitly&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create an authenticated API client using the token you created in your Bitly account:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;client = Bitly::API::Client.new(
  token: &quot;Enter your access token here&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Shortening a URL&lt;/h3&gt;
&lt;p&gt;You can now use this &lt;code&gt;client&lt;/code&gt; object to access all the Bitly APIs. For example, you can shorten a URL to a Bitlink like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;long_url = &quot;https://twitter.com/philnash&quot;
bitlink = client.shorten(long_url: long_url)
bitlink.link
# =&amp;gt; &quot;https://bit.ly/3zYdN21&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://dev.bitly.com/api-reference/#createBitlink&quot;&gt;shorten endpoint&lt;/a&gt; is a simplified method of shortening a URL. You can also use the &lt;a href=&quot;https://dev.bitly.com/api-reference/#createBitlink&quot;&gt;create endpoint&lt;/a&gt; and set other attributes, like adding a title, tags or deeplinks into native applications.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;long_url = &quot;https://twitter.com/philnash&quot;
bitlink = client.create_bitlink(
  long_url: long_url,
  title: &quot;Phil Nash on Twitter&quot;,
  tags: [&quot;social media&quot;, &quot;worth following&quot;]
)
bitlink.link
# =&amp;gt; &quot;https://bit.ly/3zYdN21&quot;
bitlink.title
# =&amp;gt; &quot;Phil Nash on Twitter&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Expanding a URL&lt;/h3&gt;
&lt;p&gt;The API client can also be used to expand Bitlinks. You can use the &lt;code&gt;expand&lt;/code&gt; method with any Bitlink, not just ones that you have shortened yourself. When you expand a URL you will get back publicly available information about the URL.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bitlink = client.expand(bitlink: &quot;bit.ly/3zYdN21&quot;)
bitlink.long_url
# =&amp;gt; &quot;https://twitter.com/philnash&quot;
bitlink.title
# =&amp;gt; nil
# (title is not public information)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the URL was a Bitlink from your own account you get more detailed information when you use the &lt;code&gt;bitlink&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bitlink = client.bitlink(bitlink: &quot;bit.ly/3zYdN21&quot;)
bitlink.long_url
# =&amp;gt; &quot;https://twitter.com/philnash&quot;
bitlink.title
# =&amp;gt; &quot;Phil Nash on Twitter&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Other Bitlink methods&lt;/h3&gt;
&lt;p&gt;Once you have a &lt;code&gt;bitlink&lt;/code&gt; object, you can call other methods on it. If you wanted to update the information about the link, for example, you can use the &lt;code&gt;update&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bitlink.update(title: &quot;Phil Nash on Twitter. Go follow him&quot;)
bitlink.title
# =&amp;gt; &quot;Phil Nash on Twitter. Go follow him&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also fetch metrics for your link, including the &lt;a href=&quot;https://dev.bitly.com/api-reference/#getClicksSummaryForBitlink&quot;&gt;&lt;code&gt;clicks_summary&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://dev.bitly.com/api-reference/#getClicksForBitlink&quot;&gt;&lt;code&gt;link_clicks&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://dev.bitly.com/api-reference/#getMetricsForBitlinkByCountries&quot;&gt;&lt;code&gt;click_metrics_by_country&lt;/code&gt;&lt;/a&gt;. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;click_summary = bitlink.clicks_summary
click_summary.total_clicks
# =&amp;gt; 1
# (not very popular yet)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Methods that return a list of metrics implement &lt;code&gt;Enumerable&lt;/code&gt; so you can loop through them using &lt;code&gt;each&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;country_clicks = bitlink.click_metrics_by_country
country_clicks.each do |metric|
  puts &quot;#{metric.value}: #{metric.clicks}&quot;
end
# =&amp;gt; AU: 1
# (it was just me clicking it)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these methods, you can create or fetch short links and then retrieve metrics about them. Your application can shorten links and measure their impact with a few lines of code.&lt;/p&gt;
&lt;h2&gt;There&apos;s more!&lt;/h2&gt;
&lt;p&gt;For advanced uses, you can also &lt;a href=&quot;https://github.com/philnash/bitly/blob/main/docs/authentication.md&quot;&gt;authorise other Bitly accounts to create and fetch short links via OAuth2&lt;/a&gt; as well as manage your account&apos;s &lt;a href=&quot;https://github.com/philnash/bitly/blob/main/docs/users.md&quot;&gt;users&lt;/a&gt;, &lt;a href=&quot;https://github.com/philnash/bitly/blob/main/docs/groups.md&quot;&gt;groups&lt;/a&gt;, and &lt;a href=&quot;https://github.com/philnash/bitly/blob/main/docs/organizations.md&quot;&gt;organisations&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;A useful little tool&lt;/h2&gt;
&lt;p&gt;To find out more about using the Bitly API with Ruby, you can read the &lt;a href=&quot;https://dev.bitly.com/&quot;&gt;Bitly API documentation&lt;/a&gt;, the &lt;a href=&quot;https://www.rubydoc.info/gems/bitly/&quot;&gt;Bitly gem&apos;s generated documentation&lt;/a&gt; and the &lt;a href=&quot;https://github.com/philnash/bitly/tree/main/docs&quot;&gt;docs in the GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are interested, you can also read a bit more about the &lt;a href=&quot;/blog/2020/03/17/the-story-of-a-midly-popular-ruby-gem/&quot;&gt;backstory of the Bitly Ruby gem&lt;/a&gt;. I&apos;ve been working on this project since 2009, would you believe?&lt;/p&gt;
&lt;p&gt;Are you using the Bitly API or do you have any feedback or feature requests? Let me know on &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;Twitter at @philnash&lt;/a&gt; or &lt;a href=&quot;https://github.com/philnash/bitly/issues&quot;&gt;open an issue in the repo&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 09 Nov 2022 00:00:00 GMT</pubDate><category>ruby</category><category>bitly</category><category>api</category></item><item><title>Don&apos;t ever write your own function to parse URL parameters</title><link>https://philna.sh/blog/2021/04/11/dont-ever-write-your-own-function-to-parse-url-parameters/</link><guid isPermaLink="true">https://philna.sh/blog/2021/04/11/dont-ever-write-your-own-function-to-parse-url-parameters/</guid><description>&lt;p&gt;Sometimes the platform we are building on provides more functionality than we can keep in our own heads. However, depending on the problem, we often find ourselves trying to write the code to solve the issue rather than finding and using the existing solution provided by the platform. I almost fell for this recently when trying to parse a query string.&lt;/p&gt;
&lt;h2&gt;I can do it myself&lt;/h2&gt;
&lt;p&gt;A colleagues had a query string they needed to parse in a function, and asked for recommendations on how to do so. For some reason I decided to roll up my sleeves and code directly into Slack. I came up with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function parse(input) {
  return input
    .split(&quot;&amp;amp;&quot;)
    .map((pairs) =&amp;gt; pairs.split(&quot;=&quot;))
    .reduce((acc, [k, v]) =&amp;gt; {
      acc[k] = decodeURIComponent(v);
      return acc;
    }, {});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks pretty good, right? It even uses &lt;code&gt;reduce&lt;/code&gt; which makes all array operations look extra fancy. And it works:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parse(&quot;name=Phil&amp;amp;hello=world&quot;);
// =&amp;gt; { name: &apos;Phil&apos;, hello: &apos;world&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Except for when it doesn&apos;t work. Like if the query string uses the &lt;code&gt;+&lt;/code&gt; character to encode spaces.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parse(&quot;name=Phil+Nash&quot;);
// =&amp;gt; { name: &apos;Phil+Nash&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or if you have more than one value for a key, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;parse(&quot;name=Phil&amp;amp;name=John&quot;)
// =&amp;gt; { name: &quot;John&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could have worked to fix these issues, but that would just bring up more questions. Like how should multiple values be represented? Always as an array? On as an array if there is more than one value?&lt;/p&gt;
&lt;p&gt;The problem with all of this is that even after thinking about these extra issues, there are likely more hiding out there. All this thinking and coding is a waste anyway, because we have &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams&quot;&gt;&lt;code&gt;URLSearchParams&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;URLSearchParams has a specification and several implementations&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;URLSearchParams&lt;/code&gt; class is specified in the &lt;a href=&quot;https://url.spec.whatwg.org/#urlsearchparams&quot;&gt;URL standard&lt;/a&gt; and started appearing in browsers in 2016 and in Node.js in 2017 (in version 7.5.0 as part of the &lt;code&gt;URL&lt;/code&gt; standard library module and then in version 10.0.0 in the global namespace).&lt;/p&gt;
&lt;p&gt;It handles all of the parsing problems above:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const params = new URLSearchParams(&quot;name=Phil&amp;amp;hello=world&quot;);
params.get(&quot;name&quot;);
// =&amp;gt; &quot;Phil&quot;
params.get(&quot;hello&quot;);
// =&amp;gt; world

const multiParams = new URLSearchParams(&quot;name=Phil&amp;amp;name=John&quot;);
multiParams.get(&quot;name&quot;);
// =&amp;gt; &quot;Phil&quot;
// ???
multiParams.getAll(&quot;name&quot;);
// =&amp;gt; [ &apos;Phil&apos;, &apos;John&apos; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it handles more, like iterating over the parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for (const [key, value] of params) {
  console.log(`${key}: ${value}`)
}
// =&amp;gt; name: Phil
// =&amp;gt; hello: world

for (const [key, value] of multiParams) {
  console.log(`${key}: ${value}`)
}
// =&amp;gt; name: Phil
// =&amp;gt; name: John
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or adding to the parameters and serialising back to a query string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;multiParams.append(&quot;name&quot;, &quot;Terry&quot;);
multiParams.append(&quot;favouriteColour&quot;, &quot;red&quot;);
multiParams.toString();
// =&amp;gt; &apos;name=Phil&amp;amp;name=John&amp;amp;name=Terry&amp;amp;favouriteColour=red&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final thing I like about &lt;code&gt;URLSearchParams&lt;/code&gt; is that it is available in both the browser and Node.js. Node.js has had the &lt;a href=&quot;https://nodejs.org/api/querystring.html&quot;&gt;&lt;code&gt;querystring&lt;/code&gt; module&lt;/a&gt; since version 0.10.0, but when APIs like this are available on the client and server side, then JavaScript developers can be more productive regardless of the environment in which they are working.&lt;/p&gt;
&lt;p&gt;As an aside, one of the things I appreciate about the Deno project is &lt;a href=&quot;https://deno.land/manual@v1.8.3/runtime/web_platform_apis&quot;&gt;their aim for Deno to use web platform APIs where possible&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Use the platform that is available&lt;/h2&gt;
&lt;p&gt;This post started as a story about choosing to write code to solve a problem that the platform already had solved. Once I realised my mistake I jumped straight back into Slack to correct myself and recommend &lt;code&gt;URLSearchParams&lt;/code&gt;. When you understand the capabilities of the platform you are working with you can both code more efficiently and avoid bugs.&lt;/p&gt;
&lt;p&gt;I never have to write code to parse URL parameters, I wrote this post to remind myself of that. You never have to either.&lt;/p&gt;
</description><pubDate>Sun, 11 Apr 2021 00:00:00 GMT</pubDate><category>javascript</category><category>web</category><category>node</category></item><item><title>Restart your app and not your tunnel with ngrok and nodemon</title><link>https://philna.sh/blog/2021/03/15/restart-app-not-tunnel-ngrok-nodemon/</link><guid isPermaLink="true">https://philna.sh/blog/2021/03/15/restart-app-not-tunnel-ngrok-nodemon/</guid><description>&lt;p&gt;When I am developing web applications in Node.js, I like the server to restart when I make changes, so I use &lt;a href=&quot;https://nodemon.io/&quot;&gt;nodemon&lt;/a&gt;. When I am developing an application that consumes &lt;a href=&quot;https://www.twilio.com/docs/glossary/what-is-a-webhook&quot;&gt;webhooks&lt;/a&gt; or that I want to share publicly, &lt;a href=&quot;https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html&quot;&gt;I use ngrok&lt;/a&gt;. In fact, I like ngrok so much, I volunteered to help maintain the &lt;a href=&quot;https://github.com/bubenshchykov/ngrok/&quot;&gt;Node.js wrapper for ngrok&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, you can run ngrok and nodemon separately and things work fine. But what if you always want to run them together and you want just one command to do that. Since nodemon is a Node package and ngrok has a Node wrapper, we can do this. Here&apos;s how.&lt;/p&gt;
&lt;h2&gt;An example with a basic Express app&lt;/h2&gt;
&lt;p&gt;You might already have an application you want to do with this, but for the purposes of the post, let&apos;s create an example &lt;a href=&quot;https://expressjs.com/&quot;&gt;Express&lt;/a&gt; application. We can create a basic application with the &lt;a href=&quot;https://expressjs.com/en/starter/generator.html&quot;&gt;Express generator&lt;/a&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx express-generator test-app
cd test-app
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the application with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open your browswer to &lt;code&gt;localhost:3000&lt;/code&gt; and you will see the welcome page. You can also open &lt;code&gt;localhost:3000/users&lt;/code&gt; and it will say &quot;respond with a resource&quot;. Open &lt;code&gt;routes/users.js&lt;/code&gt; and change the route from:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;router.get(&apos;/&apos;, function(req, res, next) {
  res.send(&apos;respond with a resource&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;router.get(&apos;/&apos;, function(req, res, next) {
  res.send(&apos;respond with a list of users&apos;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Refresh &lt;code&gt;localhost:3000/users&lt;/code&gt; and you will see it still returns &quot;respond with a resource&quot;. Stop the application, restart it with &lt;code&gt;npm start&lt;/code&gt;, and when you reload the page it will have changed. We can make this better with nodemon.&lt;/p&gt;
&lt;h2&gt;Starting the app with nodemon&lt;/h2&gt;
&lt;p&gt;To start the application with nodemon, you first need to install the package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install nodemon --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then run the application by opening &lt;code&gt;package.json&lt;/code&gt; and changing the start script from &lt;code&gt;&quot;start&quot;: &quot;node ./bin/www&quot;&lt;/code&gt; to &lt;code&gt;&quot;start&quot;: &quot;nodemon ./bin/www&quot;&lt;/code&gt;. This works great and your application now restarts when you make changes.&lt;/p&gt;
&lt;h2&gt;Adding ngrok to the mix&lt;/h2&gt;
&lt;p&gt;This all works great with nodemon on its own, but now we want to give the application a public URL while we are developing it. We can use ngrok for this and we can build it in using the &lt;a href=&quot;https://github.com/bubenshchykov/ngrok&quot;&gt;ngrok Node package&lt;/a&gt;. Start by installing ngrok:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install ngrok --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we could add ngrok to the &lt;code&gt;./bin/www&lt;/code&gt; script that the Express generator created for us. But if you do this, then every time you change something, nodemon will restart the application and your ngrok tunnel. If you&apos;re using a free or unregistered ngrok account then your ngrok URL will keep changing on every restart. Instead, let&apos;s build a script that starts an ngrok tunnel and then uses nodemon to run the application script &lt;code&gt;./bin/www&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create a new file in the &lt;code&gt;bin&lt;/code&gt; directoy called &lt;code&gt;./bin/dev&lt;/code&gt;. You might need to make this file executable with &lt;code&gt;chmod 755 ./bin/dev&lt;/code&gt;. Open it in your editor.&lt;/p&gt;
&lt;p&gt;Start by adding a &lt;a href=&quot;https://en.wikipedia.org/wiki/Shebang_(Unix)&quot;&gt;shebang&lt;/a&gt; for Node. We&apos;ll also add in a protection to make sure this script isn&apos;t run in production.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node

if (process.env.NODE_ENV === &quot;production&quot;) {
  console.error(
    &quot;Do not use nodemon in production, run bin/www directly instead.&quot;
  );
  process.exitCode = 1;
  return;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, if the environment variable &lt;code&gt;NODE_ENV&lt;/code&gt; is set to production the script will just return early.&lt;/p&gt;
&lt;p&gt;Next, require the ngrok and nodemon packages.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ngrok = require(&quot;ngrok&quot;);
const nodemon = require(&quot;nodemon&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use ngrok to open up a tunnel connecting to a port 3000 on localhost, the port that Express uses. To open the tunnel we call on the &lt;a href=&quot;https://github.com/bubenshchykov/ngrok#connect&quot;&gt;&lt;code&gt;connect&lt;/code&gt; method of ngrok&lt;/a&gt; which returns a promise that resolves with the URL of the ngrok tunnel.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ngrok
  .connect({
    proto: &quot;http&quot;,
    addr: &quot;3000&quot;,
  })
  .then(url =&amp;gt; {
    console.log(url);
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can run this in the terminal with &lt;code&gt;./bin/dev&lt;/code&gt;, you will see an ngrok URL logged. So now we have started the ngrok tunnel, but we aren&apos;t yet running the Node application.&lt;/p&gt;
&lt;p&gt;Let&apos;s make the logging a bit nicer and then move on to starting the application with nodemon.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ngrok
  .connect({
    proto: &quot;http&quot;,
    addr: &quot;3000&quot;,
  })
  .then(url =&amp;gt; {
    console.log(`ngrok tunnel opened at: ${url}`);
    console.log(&quot;Open the ngrok dashboard at: https://localhost:4040\n&quot;);

    nodemon({
      script: &quot;./bin/www&quot;,
      exec: `NGROK_URL=${url} node`,
    });
  })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we call on nodemon and pass two arguments via an object. The &lt;code&gt;script&lt;/code&gt; is the file we want to run to start the application, in this case &lt;code&gt;./bin/www&lt;/code&gt;. The &lt;code&gt;exec&lt;/code&gt; option tells nodemon what script to execute to run the script. We set the &lt;code&gt;NGROK_URL&lt;/code&gt; environment variable to the URL that ngrok created for us so that we can refer to the ngrok URL within the application if we need it. Then the rest of the &lt;code&gt;exec&lt;/code&gt; command is &lt;code&gt;node&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Start the application with &lt;code&gt;./bin/dev&lt;/code&gt; and you will see the application start up. You can load it at &lt;code&gt;localhost:3000&lt;/code&gt; or at the ngrok URL that is logged. You will also find that if you change the response in &lt;code&gt;routes/users.js&lt;/code&gt; then it will update on the next refresh. Now you have ngrok and nodemon working together.&lt;/p&gt;
&lt;h3&gt;Finessing the script&lt;/h3&gt;
&lt;p&gt;This is working now, but there are a couple more things we can do to improve the script. We can listen to events on nodemon to give us more information about what is happening to the application and when the underlying application quits, we should close the ngrok tunnel too. We should also catch any errors that might happen to ngrok when connecting a tunnel. Here&apos;s the full script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env node

if (process.env.NODE_ENV === &quot;production&quot;) {
  console.error(
    &quot;Do not use nodemon in production, run bin/www directly instead.&quot;
  );
  process.exitCode = 1;
  return;
}

const ngrok = require(&quot;ngrok&quot;);
const nodemon = require(&quot;nodemon&quot;);

ngrok
  .connect({
    proto: &quot;http&quot;,
    addr: &quot;3000&quot;,
  })
  .then((url) =&amp;gt; {
    console.log(`ngrok tunnel opened at: ${url}`);
    console.log(&quot;Open the ngrok dashboard at: https://localhost:4040\n&quot;);

    nodemon({
      script: &quot;./bin/www&quot;,
      exec: `NGROK_URL=${url} node`,
    }).on(&quot;start&quot;, () =&amp;gt; {
      console.log(&quot;The application has started&quot;);
    }).on(&quot;restart&quot;, files =&amp;gt; {
      console.group(&quot;Application restarted due to:&quot;)
      files.forEach(file =&amp;gt; console.log(file));
      console.groupEnd();
    }).on(&quot;quit&quot;, () =&amp;gt; {
      console.log(&quot;The application has quit, closing ngrok tunnel&quot;);
      ngrok.kill().then(() =&amp;gt; process.exit(0));
    });
  })
  .catch((error) =&amp;gt; {
    console.error(&quot;Error opening ngrok tunnel: &quot;, error);
    process.exitCode = 1;
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go back to &lt;code&gt;package.json&lt;/code&gt; change the &lt;code&gt;start&lt;/code&gt; script back to &lt;code&gt;node ./bin/www&lt;/code&gt; and add a new script to run the application in dev mode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node ./bin/www&quot;,
    &quot;dev&quot;: &quot;node ./bin/dev&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can start your application with &lt;code&gt;npm run dev&lt;/code&gt; and it will use nodemon to restart on file changes and open an ngrok tunnel which doesn&apos;t change when the application restarts.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/nodemon-ngrok-working-together.png&quot; alt=&apos;A terminal window showing the application running with both nodemon and ngrok. The logs show that some pages have been visited, then one of the routes changed and the app was reloaded.&apos; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Nodemon and ngrok working in tandem&lt;/h2&gt;
&lt;p&gt;You can adjust the script above to work for any of your applications. Indeed, aside from needing Node to run the nodemon and ngrok packages, you could use this for any application you are building. For more details, check out the &lt;a href=&quot;https://github.com/remy/nodemon#nodemon&quot;&gt;nodemon documentation&lt;/a&gt; and the &lt;a href=&quot;https://github.com/bubenshchykov/ngrok#ngrok&quot;&gt;ngrok documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are a VS Code user and you prefer having ngrok at the tip of your command prompt, take a look at my &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=philnash.ngrok-for-vscode&quot;&gt;ngrok for VS Code plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The script in this post was inspired by &lt;a href=&quot;https://gist.github.com/spalladino/45a6e54d7942ac0bad64dd54d7d12467&quot;&gt;Santiago Palladino&apos;s version&lt;/a&gt; which I brought up to date and added usage instructions. Thanks to Santiago, &lt;a href=&quot;https://github.com/bubenshchykov&quot;&gt;Alex Bubenshchykov&lt;/a&gt;, the author of the ngrok Node package, &lt;a href=&quot;https://remysharp.com/&quot;&gt;Remy Sharp&lt;/a&gt;, the author of nodemon, and &lt;a href=&quot;https://inconshreveable.com/&quot;&gt;Alan Shreve&lt;/a&gt;, the creator of ngrok, all for making this possible.&lt;/p&gt;
</description><pubDate>Mon, 15 Mar 2021 00:00:00 GMT</pubDate><category>node</category><category>ngrok</category><category>nodemon</category><category>webdev</category></item><item><title>How to display dates in your user&apos;s time zone with the Intl API</title><link>https://philna.sh/blog/2021/02/22/display-dates-in-your-users-time-zone/</link><guid isPermaLink="true">https://philna.sh/blog/2021/02/22/display-dates-in-your-users-time-zone/</guid><description>&lt;p&gt;Time zones are hard. Not only are there a lot of them, but they don&apos;t fit nicely into whole hour blocks, daylight savings time changes individual zones some of the time, and zones move around and change all the time. In short, it is a hassle.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/time_zones.png&quot; alt=&quot;A map of time zones across the world&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;figcaption&amp;gt;&amp;lt;a href=&quot;https://commons.wikimedia.org/wiki/File:World_Time_Zones_Map.png&quot;&amp;gt;Time zones of the world, via Wikimedia Commons&amp;lt;/a&amp;gt;, aren&apos;t they nice and easy?&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Events are happening online all the time and since people can join an online event from anywhere it is important to tell people what time that event will be happening for them. We need to combine an event&apos;s time, a user&apos;s time zone, and even their language and formatting preferences, to show a user a meaningful time.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl&quot;&gt;the &lt;code&gt;Intl&lt;/code&gt; API&lt;/a&gt;, the ECMAScript Internationalisation API, and specifically &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat&quot;&gt;&lt;code&gt;Intl.DateTimeFormat&lt;/code&gt;&lt;/a&gt; which will aid us with date and time functions to achieve just that.&lt;/p&gt;
&lt;h2&gt;Get a user&apos;s time zone in JavaScript&lt;/h2&gt;
&lt;p&gt;There are two keys to getting a time correct in a user&apos;s time zone: the time in its original time zone and the user&apos;s time zone. If you have the time of an event, then it&apos;s up to you to provide that with its time zone, ideally in &lt;a href=&quot;https://en.wikipedia.org/wiki/ISO_8601&quot;&gt;ISO 8601 format&lt;/a&gt;. But what can we do about getting the user&apos;s time zone?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; is an object that can help us format the time. It also comes with a utility that tells us more about the user&apos;s time. Running &lt;code&gt;Intl.DateTimeFormat().resolvedOptions()&lt;/code&gt; in a browser tells us everything the browser knows about the user&apos;s date and time preferences. If I run it for myself I get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Intl.DateTimeFormat().resolvedOptions();
// =&amp;gt; {
//   locale: &quot;en-AU&quot;,
//   calendar: &quot;gregory&quot;,
//   numberingSystem: &quot;latn&quot;,
//   timeZone: &quot;Australia/Melbourne&quot;,
//   year: &quot;numeric&quot;,
//   month: &quot;2-digit&quot;,
//   day: &quot;2-digit&quot;,
// };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that the browser knows I am in the &quot;Australia/Melbourne&quot; time zone. As I write this, that&apos;s Australian Eastern Daylight Time (AEDT), UTC+11. Between some time in April and October it will be Australian Eastern Standard Time (AEST), which is only UTC+10. See what I mean about this being hard? The place stays the same, but the time zone can change depending on the time of year. The good news is that &lt;code&gt;Intl&lt;/code&gt; is aware of when that changes, so it can give us the time zone &quot;Australia/Melbourne&quot; and internally know when that translates to AEDT or AEST.&lt;/p&gt;
&lt;p&gt;Now we have the time zone, we need to take a time and print it out for the time zone.&lt;/p&gt;
&lt;h2&gt;Showing a time in a time zone&lt;/h2&gt;
&lt;p&gt;The main job of &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; is to format a time for a user. It takes a language and then a bunch of options, including the time zone, and returns a formatter function. That function takes a datetime object as an argument and returns a string that you can display to the user.&lt;/p&gt;
&lt;p&gt;I can create a formatter that will format times for my time zone like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const formatter = Intl.DateTimeFormat(&quot;en-AU&quot;, { timeZone: &quot;Australia/Melbourne&quot; });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I can use that formatter to print out a date. I&apos;ll use the date I am writing this as an example, that way I can just use &lt;code&gt;new Date()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const date = new Date();
formatter.format(date);
// =&amp;gt; &quot;22/02/2021&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don&apos;t actually need to provide a language or time zone to the formatter, it will pick the system defaults.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const minimalFormatter = Intl.DateTimeFormat();
minimalFormatter.format(date);
// =&amp;gt; &quot;22/02/2021&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default, the formatter outputs a short date form, even though the time is in there too. This is useful though, as we can still see that the time zone side of things is working. It may be the 22nd as I write this, but on the West coast of the USA it&apos;s still the 21st. We can see this by setting the time zone to something like &quot;America/Los_Angeles&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const USFormatter = Intl.DateTimeFormat(&quot;en-AU&quot;, { timeZone: &quot;America/Los_Angeles&quot; });
USFormatter.format(date);
// =&amp;gt; &quot;21/02/2021&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A day behind, as expected. Note that &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; takes a language string too. I&apos;ve been using Australian English as the setting, but we can set this to US English too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const reallyUSFormatter = Intl.DateTimeFormat(&quot;en-US&quot;, { timeZone: &quot;America/Los_Angeles&quot; });
reallyUSFormatter.format(date);
// =&amp;gt; &quot;02/21/2021&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we get the date in mm/dd/yyyy format, far and away &lt;em&gt;the worst format&lt;/em&gt;. But, since &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; knows international preferences for this, I can provide the language and the result will be formatted in the way the user expects and I never have to see a date with the month in the most significant place.&lt;/p&gt;
&lt;p&gt;As an aside, there are a few ways to get a user&apos;s preferred language in the browser. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages&quot;&gt;&lt;code&gt;navigator.languages&lt;/code&gt;&lt;/a&gt; returns an array of a user&apos;s preferred languages. &lt;code&gt;navigator.language&lt;/code&gt; is supposed to return the first element of &lt;code&gt;navigator.languages&lt;/code&gt;, but some browsers disagree and return the language of the browser UI, which is not necessarily the same. See more about &lt;a href=&quot;https://caniuse.com/mdn-api_navigatorlanguage_languages&quot;&gt;the inconsistencies in this API on CanIUse&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Formatting the time&lt;/h3&gt;
&lt;p&gt;Now we know how to create a formatter that outputs a short date format, we need to know how to use it to format times the way we want. We saw that &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt; takes a language and then an object of options, including &lt;code&gt;timeZone&lt;/code&gt;, to create a formatter. It is that object of options that we can use to add or remove parts of the date and time in the output. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#parameters&quot;&gt;MDN has great documentation on the parameters for &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt;&lt;/a&gt;, but let&apos;s take a quick look at some options.&lt;/p&gt;
&lt;p&gt;In the examples above, we gave no input for how we wanted to format the date. This is the equivalent of passing the defaults we saw above in the response to &lt;code&gt;Intl.DateTimeFormat().resolvedOptions()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  day: &quot;2-digit&quot;,
  month: &quot;2-digit&quot;,
  year: &quot;numeric&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;day&lt;/code&gt;, &lt;code&gt;month&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt; options can be set to &quot;numeric&quot; or &quot;2-digit&quot; and &lt;code&gt;month&lt;/code&gt; can also be &quot;long&quot;, &quot;short&quot; or &quot;narrow&quot;. Let&apos;s see what happens if we change things:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const newFormatter = Intl.DateTimeFormat(&quot;en-AU&quot;, {
  timeZone: &quot;Australia/Melbourne&quot;,
  month: &quot;long&quot;,
  year: &quot;2-digit&quot;
});
newFormatter.format(date);
// =&amp;gt; &quot;February 21&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case I defined &lt;code&gt;month&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt; and left &lt;code&gt;date&lt;/code&gt; off, so &lt;code&gt;date&lt;/code&gt; no longer appears. Using &quot;2-digit&quot; for the &lt;code&gt;year&lt;/code&gt; changes the output from &quot;2021&quot; to &quot;21&quot; and using &quot;long&quot; for the &lt;code&gt;month&lt;/code&gt; prints out the full name of the month, &quot;February&quot;.&lt;/p&gt;
&lt;p&gt;We can add other elements to this format too, like &lt;code&gt;weekday&lt;/code&gt;, &lt;code&gt;era&lt;/code&gt;, &lt;code&gt;hour&lt;/code&gt;, &lt;code&gt;minute&lt;/code&gt;, &lt;code&gt;second&lt;/code&gt; and &lt;code&gt;timeZoneName&lt;/code&gt;. Here&apos;s a fully written out date time:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fullFormatter = Intl.DateTimeFormat(&quot;en-AU&quot;, {
  timeZone: &quot;Australia/Melbourne&quot;,
  day: &quot;numeric&quot;,
  month: &quot;long&quot;,
  year: &quot;numeric&quot;,
  weekday: &quot;long&quot;,
  era: &quot;short&quot;,
  hour: &quot;numeric&quot;,
  minute: &quot;numeric&quot;,
  second: &quot;numeric&quot;,
  timeZoneName: &quot;short&quot;
});
fullFormatter.format(date);
// =&amp;gt; &quot;Monday, 22 February 2021 AD, 5:05:52 pm AEDT&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a couple of shortcuts you can use too; &lt;code&gt;timeStyle&lt;/code&gt; and &lt;code&gt;dateStyle&lt;/code&gt; can be &quot;full&quot;, &quot;long&quot;, &quot;medium&quot; or &quot;short&quot; and you can use them together, but not with the above options.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const shortcutFormatter = Intl.DateTimeFormat(&quot;en-AU&quot;, {
  timeZone: &quot;Australia/Melbourne&quot;,
  timeStyle: &quot;long&quot;,
  dateStyle: &quot;short&quot;
});
shortcutFormatter.format(date);
// =&amp;gt; &quot;22/2/21, 5:05:52 pm AEDT&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;When is your event?&lt;/h2&gt;
&lt;p&gt;Now, we can take all this knowledge and apply it to the date of an event. As I write this, it is the 22nd February, so let&apos;s consider an event in the future for me. Say it will occur on Friday 26th February, at 10am in the morning Pacific Time (UTC-8).&lt;/p&gt;
&lt;p&gt;First we convert that time to &lt;a href=&quot;https://262.ecma-international.org/11.0/#sec-date-time-string-format&quot;&gt;ECMAScript compatible ISO 8601&lt;/a&gt; format: &lt;code&gt;2021-02-26T18:00:00.000Z&lt;/code&gt; (Z means UTC with no offset, so 10am Pacific Time is 6pm in UTC). Other formats are supported by browsers, but by convention only, not as part of the standard.&lt;/p&gt;
&lt;p&gt;We take the date string and create a new &lt;code&gt;Date&lt;/code&gt; object with it. We also create a date formatter. We feed the date to the formatter and we get the date out in the correct time zone and format.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const excitingEventTime = &quot;2021-02-26T18:00:00.000Z&quot;;
const eventDate = new Date(excitingEventTime);
const formatter = Intl.DateTimeFormat(&quot;en-AU&quot;, {
  timeZone: &quot;Australia/Melbourne&quot;,
  day: &quot;numeric&quot;,
  month: &quot;long&quot;,
  year: &quot;numeric&quot;,
  weekday: &quot;long&quot;,
  hour: &quot;numeric&quot;,
  minute: &quot;numeric&quot;,
  second: &quot;numeric&quot;,
  timeZoneName: &quot;short&quot;
});
formatter.format(eventDate);
// =&amp;gt; Saturday, 27 February 2021, 5:00:00 am AEDT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So there we go, Friday morning events in Pacific Time happen early Saturday morning in AEDT.&lt;/p&gt;
&lt;h2&gt;Use Intl more&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Intl&lt;/code&gt; APIs, and in particular &lt;code&gt;Intl.DateTimeFormat&lt;/code&gt;, make it easier for us as developers to display datetimes in the format, the time zone and the language of our users.&lt;/p&gt;
&lt;p&gt;As a bonus and because &lt;code&gt;Intl&lt;/code&gt; is part of ECMAScript, it is also available in Node.js. You will want to be more careful with defaults when working on server though. The time zone, for example, is going to be where the server is located, not the time zone of your user. I recommend working in UTC on servers and using users&apos; time zones when displaying the datetime.&lt;/p&gt;
&lt;p&gt;I recommend you familiarise yourself with everything that is available under &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl&quot;&gt;&lt;code&gt;Intl&lt;/code&gt;&lt;/a&gt; so you understand how you can use the platform to make your applications work better for your users wherever they are.&lt;/p&gt;
</description><pubDate>Mon, 22 Feb 2021 00:00:00 GMT</pubDate><category>javascript</category><category>web</category><category>time</category><category>i18n</category></item><item><title>How to stream file downloads in Node.js with Got</title><link>https://philna.sh/blog/2020/08/06/how-to-stream-file-downloads-in-node-js-with-got/</link><guid isPermaLink="true">https://philna.sh/blog/2020/08/06/how-to-stream-file-downloads-in-node-js-with-got/</guid><description>&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/got&quot;&gt;Got is a Node.js library for making HTTP requests&lt;/a&gt;. It has both promise and stream based APIs and in this post I want to explore how to use the stream API to download files.&lt;/p&gt;
&lt;h2&gt;Using Got&lt;/h2&gt;
&lt;p&gt;If you use HTTP libraries for making API requests, then the promise method is likely the best for you. Making a basic HTTP request with Got looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const got = require(&quot;got&quot;);

got(url)
  .then(response =&amp;gt; console.log(response.body))
  .catch(error =&amp;gt; console.log(error.response.body));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The stream API gives us some extra benefits though. The promise API will load responses into memory until the response is finished before fulfilling the promise, but with the stream API you can act on chunks of the response as they arrive. This makes streams more memory efficient, particularly for large responses.&lt;/p&gt;
&lt;h2&gt;Streaming a file download with Got&lt;/h2&gt;
&lt;p&gt;You can create a stream with Got using the &lt;code&gt;stream&lt;/code&gt; method or by setting &lt;code&gt;isStream&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in the options.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;got.stream(url);
// or
got(url, { isStream: true });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A Got stream is a &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_class_stream_duplex&quot;&gt;duplex stream&lt;/a&gt;, which means it is both readable and writable. For the purposes of downloading a file, we will just be using its readable properties.&lt;/p&gt;
&lt;p&gt;To download a file we need to send the response to the file system somehow. Streams allow you to pipe the data from one stream to another. To write to the file system we can create a writable stream using the &lt;a href=&quot;https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options&quot;&gt;&lt;code&gt;fs&lt;/code&gt; module&apos;s &lt;code&gt;createWriteStream&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To test this out we&apos;ll need a file we can stream. The URL in the following examples is a 500KB gif that you might like.&lt;/p&gt;
&lt;p&gt;The simplest way to use a Got stream and write the file to the file system looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const got = require(&quot;got&quot;);
const { createWriteStream } = require(&quot;fs&quot;);

const url =
  &quot;https://media0.giphy.com/media/4SS0kfzRqfBf2/giphy.gif&quot;;

got.stream(url).pipe(createWriteStream(&apos;image.gif&apos;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code creates a Got stream of the image URL and pipes the data to a stream that writes the data into a file called &quot;image.jpg&quot;.&lt;/p&gt;
&lt;h2&gt;Handling progress and errors&lt;/h2&gt;
&lt;p&gt;The above code will download the file as long as there are no problems. If an error occurs the code will crash with an unhandled exception. There is also no feedback, so if your file is large you will not see any result until the download is complete. We can listen to events on the stream to handle both of these cases.&lt;/p&gt;
&lt;p&gt;Let&apos;s start by rearranging the code above. We&apos;ll get individual handles to the Got stream and the file writer stream.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const got = require(&quot;got&quot;);
const { createWriteStream } = require(&quot;fs&quot;);

const url = &quot;https://media0.giphy.com/media/4SS0kfzRqfBf2/giphy.gif&quot;;
const fileName = &quot;image.gif&quot;;

const downloadStream = got.stream(url);
const fileWriterStream = createWriteStream(fileName);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, before we pipe the &lt;code&gt;downloadStream&lt;/code&gt; into the &lt;code&gt;fileWriterStream&lt;/code&gt; attach some event listeners.&lt;/p&gt;
&lt;p&gt;To get feedback on the progress of the download we can listen to the &lt;code&gt;downloadProgress&lt;/code&gt; event on the &lt;code&gt;downloadStream&lt;/code&gt;. The event fires with an object with 3 properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;transferred&lt;/code&gt;: the number of bytes transferred so far&lt;/li&gt;
&lt;li&gt;&lt;code&gt;total&lt;/code&gt;: the total number of bytes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;percent&lt;/code&gt;: the proportion of the transfer that is complete (between 0 and 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the server you are downloading from doesn&apos;t return a &lt;code&gt;Content-Length&lt;/code&gt; header for the file, then &lt;code&gt;total&lt;/code&gt; will be undefined and &lt;code&gt;percent&lt;/code&gt; will be 0 until the download is complete.&lt;/p&gt;
&lt;p&gt;We can handle errors on both the &lt;code&gt;downloadStream&lt;/code&gt; and &lt;code&gt;fileWriterStream&lt;/code&gt; by listening for the &lt;code&gt;error&lt;/code&gt; event. It is good to handle both of these errors as it gives us information over what failed. If the &lt;code&gt;downloadStream&lt;/code&gt; emits an error then there is a problem with the URL, the network or the remote server. If the &lt;code&gt;fileWriterStream&lt;/code&gt; emits an error then there is a problem with the file system.&lt;/p&gt;
&lt;p&gt;For one last piece of feedback, we can also listen to the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_event_finish&quot;&gt;&lt;code&gt;finish&lt;/code&gt; event&lt;/a&gt; on the &lt;code&gt;fileWriterStream&lt;/code&gt;. This event is fired once all data has been written to the file system.&lt;/p&gt;
&lt;p&gt;Let&apos;s complete the above code by adding these events and piping the &lt;code&gt;downloadStream&lt;/code&gt; to the &lt;code&gt;fileWriterStream&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const got = require(&quot;got&quot;);
const { createWriteStream } = require(&quot;fs&quot;);

const url = &quot;https://media0.giphy.com/media/4SS0kfzRqfBf2/giphy.gif&quot;;
const fileName = &quot;image.gif&quot;;

const downloadStream = got.stream(url);
const fileWriterStream = createWriteStream(fileName);

downloadStream
  .on(&quot;downloadProgress&quot;, ({ transferred, total, percent }) =&amp;gt; {
    const percentage = Math.round(percent * 100);
    console.error(`progress: ${transferred}/${total} (${percentage}%)`);
  })
  .on(&quot;error&quot;, (error) =&amp;gt; {
    console.error(`Download failed: ${error.message}`);
  });

fileWriterStream
  .on(&quot;error&quot;, (error) =&amp;gt; {
    console.error(`Could not write file to system: ${error.message}`);
  })
  .on(&quot;finish&quot;, () =&amp;gt; {
    console.log(`File downloaded to ${fileName}`);
  });

downloadStream.pipe(fileWriterStream);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run the above code in a terminal you will see your download progress logged to the terminal and the image will be downloaded successfully.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/got-download.png&quot; alt=&quot;Running the code shows the download progress up to 100% then displays that the image has been downloaded.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/picture&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Getting fancy with more stream methods&lt;/h2&gt;
&lt;p&gt;Using streams to download files is more efficient than Got&apos;s promise methods, but the code above has taken a bit of a backward step in terms of developer experience. Rather than dealing with promises, which could be simplified with &lt;code&gt;async/await&lt;/code&gt;, we now have to handle events with calbacks.&lt;/p&gt;
&lt;p&gt;We can get back to this experience using the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_stream_pipeline_streams_callback&quot;&gt;Stream module &lt;code&gt;pipeline&lt;/code&gt; function&lt;/a&gt;. &lt;code&gt;pipeline&lt;/code&gt; takes a number of streams as arguments and pipes the data between them. It also takes a callback function which is called if there is an error within the pipeline or once the pipeline is finished.&lt;/p&gt;
&lt;p&gt;This still deals with callbacks, but we can use the Util module&apos;s &lt;code&gt;promisify&lt;/code&gt; function to turn it into a promise.&lt;/p&gt;
&lt;p&gt;Putting this together, we can simplify the above code to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const got = require(&quot;got&quot;);
const { createWriteStream } = require(&quot;fs&quot;);
const stream = require(&quot;stream&quot;);
const { promisify } = require(&quot;util&quot;);
const pipeline = promisify(stream.pipeline);

const url = &quot;https://media0.giphy.com/media/4SS0kfzRqfBf2/giphy.gif&quot;;
const fileName = &quot;image.gif&quot;;

const downloadStream = got.stream(url);
const fileWriterStream = createWriteStream(fileName);

downloadStream.on(&quot;downloadProgress&quot;, ({ transferred, total, percent }) =&amp;gt; {
  const percentage = Math.round(percent * 100);
  console.error(`progress: ${transferred}/${total} (${percentage}%)`);
});

pipeline(downloadStream, fileWriterStream)
  .then(() =&amp;gt; console.log(`File downloaded to ${fileName}`))
  .catch((error) =&amp;gt; console.error(`Something went wrong. ${error.message}`));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or adding in &lt;code&gt;async/await&lt;/code&gt; for the final step:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(async () =&amp;gt; {
  try {
    await pipeline(downloadStream, fileWriterStream);
    console.log(`File downloaded to ${fileName}`);
  } catch (error) {
    console.error(`Something went wrong. ${error.message}`);
  }
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Node streams are cool 😎&lt;/h2&gt;
&lt;p&gt;Downloading a file is just one use of Node streams, you can find streams popping up all over the place. In this post we used a readable stream to download the file and a writable stream to write it to disk. You can also create readable streams of files and, if you are making &lt;code&gt;POST&lt;/code&gt; requests with Got, you can stream the upload of data too. Objects like &lt;code&gt;process.stdin&lt;/code&gt;, &lt;code&gt;process.stdout&lt;/code&gt; and &lt;code&gt;process.stderr&lt;/code&gt; are streams, as are HTTP &lt;a href=&quot;https://nodejs.org/api/http.html#http_class_http_incomingmessage&quot;&gt;requests&lt;/a&gt; and &lt;a href=&quot;https://nodejs.org/api/http.html#http_class_http_serverresponse&quot;&gt;responses&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For more on streams, check the &lt;a href=&quot;https://nodejs.org/api/stream.html#stream_stream&quot;&gt;Node.js stream documentation&lt;/a&gt; and, for more in depth understanding, this guide on &lt;a href=&quot;https://nodejs.org/en/docs/guides/backpressuring-in-streams/&quot;&gt;backpressuring in streams&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 06 Aug 2020 00:00:00 GMT</pubDate><category>node</category><category>http</category><category>webdev</category></item><item><title>I built a VSCode extension: ngrok for VSCode</title><link>https://philna.sh/blog/2020/04/14/ngrok-for-vscode/</link><guid isPermaLink="true">https://philna.sh/blog/2020/04/14/ngrok-for-vscode/</guid><description>&lt;p&gt;Over the Easter weekend, a four day weekend characterised by lockdowns all over the world, I decided to use the extra time I had at home to start a new project and learn a new skill. By the end of the weekend I was proud to release my first VSCode extension: &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=philnash.ngrok-for-vscode&quot;&gt;&lt;em&gt;ngrok for VSCode&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What&apos;s that now?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ngrok.com/&quot;&gt;ngrok&lt;/a&gt; is a command line tool built by &lt;a href=&quot;https://github.com/inconshreveable&quot;&gt;Alan Shreve&lt;/a&gt; that you can use to expose your localhost server with a publicly available URL. It&apos;s great for sharing access to an application running on your own machine, testing web applications on mobile devices or testing webhook integrations. For example, &lt;a href=&quot;https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html&quot;&gt;I&apos;m a big fan of using ngrok to test my webhooks&lt;/a&gt; when I am working with Twilio applications.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt; is my current favourite text editor, built by Microsoft and based on JavaScript (well, &lt;a href=&quot;https://github.com/microsoft/vscode&quot;&gt;mostly TypeScript&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;As I was using VSCode last week I wondered if there was an extension that made it easier to use ngrok. I had a search and found &lt;a href=&quot;https://github.com/WassimBenzarti/Ngrok-connect&quot;&gt;one under development&lt;/a&gt; and one &lt;a href=&quot;https://github.com/ceyhunkeklik/vscode-ngrok-client&quot;&gt;that started a web server as well as running ngrok&lt;/a&gt;. So I decided to build the extension I wanted to see in the marketplace.&lt;/p&gt;
&lt;h2&gt;What does it do?&lt;/h2&gt;
&lt;p&gt;With version 1 of the extension you can start an ngrok tunnel with either a port number or by choosing one of your named tunnels from your &lt;a href=&quot;https://ngrok.com/docs#config&quot;&gt;ngrok config file&lt;/a&gt;. There is one available setting, where you can set a custom path to a config file.&lt;/p&gt;
&lt;p&gt;Once a tunnel is running you can then open &lt;a href=&quot;https://ngrok.com/docs#getting-started-inspect&quot;&gt;the ngrok dashboard&lt;/a&gt; or close the tunnel.&lt;/p&gt;
&lt;p&gt;All the commands are available from the VSCode command palette.&lt;/p&gt;
&lt;p&gt;It&apos;s simple so far, but I wanted to keep the scope small and get it released.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/ngrok-for-vscode/start.png&quot; alt=&quot;An animation showing using the extension from the VSCode command palette.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;The code is all open source and &lt;a href=&quot;https://github.com/philnash/ngrok-for-vscode&quot;&gt;you can find it on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What&apos;s next?&lt;/h2&gt;
&lt;p&gt;I would love for you to try the extension out, especially if you are already an ngrok user. If it&apos;s useful then I am looking for feedback, &lt;a href=&quot;https://github.com/philnash/ngrok-for-vscode/issues&quot;&gt;bug reports and feature requests&lt;/a&gt; so I can continue to improve it.&lt;/p&gt;
&lt;p&gt;One idea I have already is to provide a &lt;a href=&quot;https://code.visualstudio.com/api/references/vscode-api#StatusBarItem&quot;&gt;Status Bar Item&lt;/a&gt; or &lt;a href=&quot;https://code.visualstudio.com/api/extension-guides/tree-view&quot;&gt;Tree View&lt;/a&gt; that can give more information on and control over currently running ngrok tunnels. I should probably work out how to write tests for the extension too.&lt;/p&gt;
&lt;h2&gt;What do you think?&lt;/h2&gt;
&lt;p&gt;I really am after feedback, so &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=philnash.ngrok-for-vscode&quot;&gt;please install &lt;em&gt;ngrok for VSCode&lt;/em&gt;&lt;/a&gt; and let me know what you think &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;on Twitter&lt;/a&gt; or via a &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=philnash.ngrok-for-vscode&amp;amp;ssr=false#review-details&quot;&gt;review on the VSCode Marketplace&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Tue, 14 Apr 2020 00:00:00 GMT</pubDate><category>vscode</category><category>ngrok</category><category>showdev</category></item><item><title>Making a responsive Twitch Embed</title><link>https://philna.sh/blog/2020/03/23/responsive-twitch-embed/</link><guid isPermaLink="true">https://philna.sh/blog/2020/03/23/responsive-twitch-embed/</guid><description>&lt;p&gt;I&apos;ve been trying out &lt;a href=&quot;https://www.twitch.tv/phil_nash/&quot;&gt;streaming live code on Twitch&lt;/a&gt; which is a lot of fun. I wanted to share on &lt;a href=&quot;https://philna.sh/&quot;&gt;my own site&lt;/a&gt; that I was streaming so I have built &lt;a href=&quot;/live/&quot;&gt;a page dedicated to it&lt;/a&gt;. The page will evolve, but one of the first things I wanted to include on it was an embed of my Twitch stream and chat.&lt;/p&gt;
&lt;p&gt;Twitch makes it easy to &lt;a href=&quot;https://dev.twitch.tv/docs/embed&quot;&gt;embed your stream&lt;/a&gt; but the less obvious part is how to make that embed responsive across any browser size. So here&apos;s how to create a responsive Twitch embedded stream.&lt;/p&gt;
&lt;h2&gt;The HTML&lt;/h2&gt;
&lt;p&gt;There are 3 ways you can embed Twitch on your site, &lt;a href=&quot;https://dev.twitch.tv/docs/embed/everything&quot;&gt;embedding everything&lt;/a&gt;, which involves loading the Twitch Embed JavaScript that creates an &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; on the page, &lt;a href=&quot;https://dev.twitch.tv/docs/embed/chat&quot;&gt;embedding just chat&lt;/a&gt; and &lt;a href=&quot;https://dev.twitch.tv/docs/embed/video-and-clips&quot;&gt;embedding just video&lt;/a&gt;, both of which use simple &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;s. Initially I thought I wanted to embed everything, but the video and chat within the &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; behaved weirdly and I didn&apos;t have any use for the interactive JavaScript library. Instead I plumped for embedding both the video and chat &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;s which turned out gave me much more control over the outcome.&lt;/p&gt;
&lt;p&gt;The HTML that I used looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;twitch&quot;&amp;gt;
  &amp;lt;div class=&quot;twitch-video&quot;&amp;gt;
    &amp;lt;iframe
      src=&quot;https://player.twitch.tv/?channel=phil_nash&amp;amp;parent=philna.sh&amp;amp;autoplay=false&quot;
      frameborder=&quot;0&quot;
      scrolling=&quot;no&quot;
      allowfullscreen=&quot;true&quot;
      height=&quot;100%&quot;
      width=&quot;100%&quot;&amp;gt;
    &amp;lt;/iframe&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class=&quot;twitch-chat&quot;&amp;gt;
    &amp;lt;iframe
      frameborder=&quot;0&quot;
      scrolling=&quot;no&quot;
      src=&quot;https://www.twitch.tv/embed/phil_nash/chat?parent=philna.sh&quot;
      height=&quot;100%&quot;
      width=&quot;100%&quot;&amp;gt;
    &amp;lt;/iframe&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The layout&lt;/h2&gt;
&lt;p&gt;The video is in 16:9 aspect ratio. On mobile I want the chat to sit below the video and then when the screen becomes wide enough on the right hand side of the video. When side-by-side, the chat and video should be the same height.&lt;/p&gt;
&lt;p&gt;The tricky part here is the aspect ratio. CSS doesn&apos;t have a native way to accomplish aspect ratios &lt;a href=&quot;https://www.smashingmagazine.com/2019/03/aspect-ratio-unit-css/&quot;&gt;yet&lt;/a&gt;. For now the way to force an aspect ratio in an element is unintuitive, but at least it works. You can apply this to any HTML you need to maintain a height that is relative to its width, things like &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; elements, or &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; embeds like YouTube or Twitch, as in this case.&lt;/p&gt;
&lt;h3&gt;Aspect ratios with CSS&lt;/h3&gt;
&lt;p&gt;The trick is to abuse how padding works on block elements. When you use percentage based top or bottom padding, that percentage is based on the width of the element. If we set the height of an element to 0, the width to 100% and the top padding to 75% then the element achieves an aspect ratio of 4:3. Change the padding to 56.25% and you have a 16:9 ratio.&lt;/p&gt;
&lt;p&gt;I told you it was unintuitive. But it works.&lt;/p&gt;
&lt;p&gt;You might have noticed that if we are just padding the top of the element then there is no actual space for the content. We solve this with absolute positioning, placing the &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; back at the top of the element and giving it a height and width of 100% to fill out the space.&lt;/p&gt;
&lt;p&gt;With the above HTML, the following CSS lays out the &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; with a 16:9 aspect ratio:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.twitch .twitch-video {
  padding-top: 56.25%;
  position: relative;
  height: 0;
}

.twitch .twitch-video iframe {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To complete the basic layout the chat &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; appears below the video and behaves as a regular block level element. I set a height to give the chat enough vertical space:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.twitch .twitch-chat {
  height: 400px;
}

.twitch .twitch-chat iframe {
  width: 100%;
  height: 100%;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create the side-by-side layout at wide screen sizes we need to alter this a bit. The video will take up 75% of the width of the container and the chat 25%. This is adjusted within a media query to only take effect above a certain width, for my site this became 850px.&lt;/p&gt;
&lt;p&gt;When making the width of the video container 75%, you have to adjust the padding with the same ratio. To maintain the 16:9 aspect ratio the top padding becomes 42.1875%.&lt;/p&gt;
&lt;p&gt;To keep the chat window the same height as the video, a little more absolute positioning is put into effect. The wrapper of both children is positioned relatively, then the chat &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is set to 25% width and placed absolutely on the right side of the wrapper:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@media screen and (min-width: 850px) {
  .twitch {
    position: relative;
  }

  .twitch .twitch-video {
    width: 75%;
    padding-top: 42.1875%;
  }

  .twitch .twitch-chat {
    width: 25%;
    height: auto;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The result&lt;/h2&gt;
&lt;p&gt;With the above HTML and CSS, the output ends up looking like this:&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/twitch/twitches.png&quot; alt=&quot;In the wide view the video and chat are side by side, in the mobile view they are on top of one another.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Correct aspect ratios and responsive layout; mission accomplished. &lt;a href=&quot;/live/&quot;&gt;Check it out live here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to use this on your own site, feel free to take this HTML and CSS. Just remember to replace the channel name in the embed &lt;code&gt;src&lt;/code&gt;. You can also learn more about &lt;a href=&quot;https://css-tricks.com/aspect-ratio-boxes/&quot;&gt;the CSS aspect ratio trick and other ways to accomplish the effect at CSS Tricks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And of course, &lt;a href=&quot;https://twitch.tv/phil_nash&quot;&gt;please join me on Twitch&lt;/a&gt; where I will be chatting development, live coding a variety of projects, and generally having fun.&lt;/p&gt;
</description><pubDate>Mon, 23 Mar 2020 00:00:00 GMT</pubDate><category>webdev</category><category>css</category><category>twitch</category></item><item><title>The story of a mildly popular Ruby gem</title><link>https://philna.sh/blog/2020/03/17/the-story-of-a-midly-popular-ruby-gem/</link><guid isPermaLink="true">https://philna.sh/blog/2020/03/17/the-story-of-a-midly-popular-ruby-gem/</guid><description>&lt;p&gt;The list on GitHub of repositories that depend on your repository is scary.&lt;/p&gt;
&lt;p&gt;There&apos;s something nice about writing and releasing a library, module, Ruby gem, whatever. It is code that you wrote that worked for you. There may be download numbers on the registry, but they are fairly abstract and don&apos;t necessarily relate to use. Seeing that there are real projects using the code that you wrote is kind of terrifying. More so when some of that code is still around from 10 years ago.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-left&quot;&amp;gt;
&amp;lt;a href=&quot;https://github.com/philnash/bitly/network/dependents?package_id=UGFja2FnZS05OTI5&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/bitly/dependents.png&quot; alt=&quot;The Bitly gem dependents page. Currently listed 639 repos and 20 packages.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;The first days of the Bitly Ruby gem&lt;/h2&gt;
&lt;p&gt;I started out in the web development industry as a front-end developer. I joined a company that built sites in Ruby on Rails and eventually worked my way from working with views to controllers, then models, background jobs, mailers. The whole application stack. At that point I believed myself to be a Rails developer. I had never written any plain Ruby though. In an effort to change this, I decided I was going to write and release a gem.&lt;/p&gt;
&lt;p&gt;As luck would have it I liked working with APIs, was building a lot of social applications at work at the time and the era of Twitter&apos;s serious 140 characters and link shortening was upon us. &lt;a href=&quot;https://bitly.com/&quot;&gt;Bitly&lt;/a&gt; announced they were releasing a new API and I took this as a chance to write my first Ruby. I made my &lt;a href=&quot;https://github.com/philnash/bitly/commit/8e8acfb2b45549f0baa879d4d6f214b5d928a314&quot;&gt;first commit to the GitHub repo on 21st January 2009&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It appears that the first commit included enough code to shorten a URL with the API. &lt;a href=&quot;https://github.com/philnash/bitly/commit/8e8acfb2b45549f0baa879d4d6f214b5d928a314#diff-0b3b8751a9471d9c3210979572e0bf28&quot;&gt;The tests were not good&lt;/a&gt;. Five days later it had grown into something I deemed worth releasing. On the 26th January 2009 I released version 0.1.0 into the world.&lt;/p&gt;
&lt;p&gt;On the 27th January 2009 &lt;a href=&quot;https://github.com/philnash/bitly/commit/d8464c54d05cb352955fa00cad8a1800d736da93&quot;&gt;I released version 0.1.1&lt;/a&gt; because I&apos;d forgotten a dependency and version 0.1.0 just didn&apos;t work.&lt;/p&gt;
&lt;p&gt;Notably these releases came before &lt;a href=&quot;https://rubygems.org/&quot;&gt;Rubygems.org&lt;/a&gt; existed. (Remember those days? Me neither.) You cannot find these early releases available there, but that&apos;s probably for the best!&lt;/p&gt;
&lt;h2&gt;It takes on a quiet life of its own&lt;/h2&gt;
&lt;p&gt;The beauty of open source software is that when you put something out into the world and other people find it useful they will also contribute back. Over the course of 2009 I received contributions from 7 different developers improving and adding functionality to this code. In 2010 I released version 0.5.0 which had support for Bitly&apos;s version 3 API (no, I was not very good at semantic versioning back then).&lt;/p&gt;
&lt;p&gt;For the next 8 years, the gem didn&apos;t need a lot more work. I guess it just worked for its users. There were some feature additions and bug fixes from those that used it. In total, over the life of the project so far, there have been 17 contributors other than me. The project has never been perfect, it was my first after all, but it did the job for plenty of people.&lt;/p&gt;
&lt;h2&gt;Your code is not you&lt;/h2&gt;
&lt;p&gt;...but it&apos;s hard to remember that at times. The Bitly project has long been my most starred project on GitHub, it sits at the top of my pinned repositories on &lt;a href=&quot;https://github.com/philnash/&quot;&gt;my profile page&lt;/a&gt; and has the &lt;a href=&quot;https://rubygems.org/profiles/philnash&quot;&gt;highest number of downloads on rubygems.org&lt;/a&gt; of my own projects. I&apos;m proud of it, but up until recently there was still code in that project from 2009. It always niggled at the back of my mind that, even though the gem worked, it wasn&apos;t a good representation of my ability to code.&lt;/p&gt;
&lt;p&gt;For this reason I embarked on a rewrite of the gem from the ground up in 2018. It was hard to motivate myself to complete it though, in fact I didn&apos;t get much beyond the OAuth2 implementation. It turns out that replacing working code is not a good reason to rewrite things, even if you don&apos;t like the existing code. In my head I was torn between rebuilding this project with the experience of 10 more years writing Ruby against using my time for other things that were important to me. Mostly my time won out, but it didn&apos;t stop me thinking about it.&lt;/p&gt;
&lt;p&gt;Then in 2019 Bitly introduced the 4th version of the API. While that meant the rewrite pivoted from re-implementing the version 3 API, it still didn&apos;t drive motivation. Further, Bitly announced they would be stopping support for the version 3 API on the 1st March 2020.&lt;/p&gt;
&lt;p&gt;Eventually that got me going and I managed to put the work in to put together what has just been released as &lt;a href=&quot;https://rubygems.org/gems/bitly/versions/2.0.0&quot;&gt;version 2 of the Bitly Ruby gem&lt;/a&gt;. I&apos;m happier with the library I have put together this time, it is better written, better tested and better documented than ever before. It is once more a project I can be proud of and I hope that many more projects can happily integrate it for their link shortening requirements.&lt;/p&gt;
&lt;h2&gt;The open source iceberg&lt;/h2&gt;
&lt;p&gt;There are many differences between a Ruby project like the &lt;a href=&quot;https://rubygems.org/gems/bitly&quot;&gt;Bitly gem&lt;/a&gt; and &lt;a href=&quot;https://rubygems.org/gems/rails&quot;&gt;Rails&lt;/a&gt;, &lt;a href=&quot;https://rubygems.org/gems/devise&quot;&gt;Devise&lt;/a&gt;, or even &lt;a href=&quot;https://rubygems.org/gems/jekyll&quot;&gt;Jekyll&lt;/a&gt;. So many open source projects are driven by a single maintainer and rely on their time and attention. They aren&apos;t core to applications, but they make developers&apos; lives easier. There&apos;s a lot of talk in the open source world about how to sustain the large projects that I think ignores the mildly popular projects. Much of that talk about sustainability focuses on making open source easier for under-represented developers to get into, open source shouldn&apos;t be the bastion of those with enough time and money to dedicate some of it to work on projects like this. But there are considerably more small projects than the flagship libraries and frameworks that can attract funding and those projects will continue to be built by those with expendable time and effort. I don&apos;t have any answers here, I just wanted to draw a little attention to the many, many open source projects in this position.&lt;/p&gt;
&lt;h2&gt;I have more code to write&lt;/h2&gt;
&lt;p&gt;I feel very fortunate to be able to find time to work on projects that allow me to practice my craft and potentially make another developer&apos;s life easier. The Bitly gem was my first foray into the world of creating an open source project and it lives on today. &lt;a href=&quot;https://support.bitly.com/hc/en-us/articles/360004395631-Migrating-from-v3-to-v4-of-the-Bitly-API&quot;&gt;Bitly have removed their support for the version 3 API&lt;/a&gt; so there should be plenty of developers looking to upgrade their implementations or the libraries they depend on. Hopefully version 2 will suit them, maybe there will be more work in this gem for me yet.&lt;/p&gt;
&lt;p&gt;The list on GitHub of repositories that depend on your repository is scary.&lt;/p&gt;
</description><pubDate>Tue, 17 Mar 2020 00:00:00 GMT</pubDate><category>open source</category><category>ruby</category><category>api</category><category>bitly</category></item><item><title>Mistakes I&apos;ve made treating file paths as strings</title><link>https://philna.sh/blog/2020/03/04/mistakes-treating-paths-as-strings/</link><guid isPermaLink="true">https://philna.sh/blog/2020/03/04/mistakes-treating-paths-as-strings/</guid><description>&lt;p&gt;Some things you do as a developer can work for you for years, then turn around and bite you when you were least expecting. These are the things that you wish another developer had told you early in your career so you never had to make the mistakes. This post is about one of those things and if you&apos;re reading this, consider it me telling you.&lt;/p&gt;
&lt;p&gt;File paths look like strings. You have a number of directories and maybe a file name with an extension at the end. You separate the directories and files with a &lt;code&gt;/&lt;/code&gt; character and the result looks like &lt;code&gt;/path/to/file&lt;/code&gt;. So you can treat them like strings, joining them or concatenating them until you pass them to another file method that is used to read from or write to the file. These were my thoughts from just a few months ago. Here&apos;s where I was wrong.&lt;/p&gt;
&lt;h2&gt;Don&apos;t forget Windows&lt;/h2&gt;
&lt;p&gt;If you develop on a Mac, like I have the privilege of doing, or Linux then you might have read the above paragraph and not noticed anything wrong. If develop on Windows you probably sighed into your cup of coffee as you read the &lt;code&gt;/&lt;/code&gt; character.&lt;/p&gt;
&lt;p&gt;It&apos;s all too easy to forget when you work with a Mac and deploy to Linux environments, like I have done for years, that &lt;a href=&quot;https://www.howtogeek.com/181774/why-windows-uses-backslashes-and-everything-else-uses-forward-slashes/&quot;&gt;Windows uses backslashes&lt;/a&gt;. It&apos;s all too painful to find out you&apos;ve made that mistake when you work on a command line tool that needs to run on both types of platform. &lt;a href=&quot;https://github.com/twilio-labs/create-twilio-function&quot;&gt;create-twilio-function&lt;/a&gt; is one such command line tool that had to go through a &lt;a href=&quot;https://github.com/twilio-labs/create-twilio-function/commit/af3031dcd5947a2abb735f7769bcd8fdb7e1aa73&quot;&gt;number&lt;/a&gt; &lt;a href=&quot;https://github.com/twilio-labs/create-twilio-function/commit/fa281c1fce15db0915a8b403c4d19b9b2422da99&quot;&gt;of changes&lt;/a&gt; from things like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir(path + &apos;/&apos; + dirName);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const path = require(&apos;path&apos;);
mkdir(path.join(pathName, dirName));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;so that it would work properly on Windows.&lt;/p&gt;
&lt;p&gt;To Windows users, I&apos;m sorry. To everyone else, when working with Node.js the &lt;a href=&quot;https://nodejs.org/api/path.html&quot;&gt;&lt;code&gt;path&lt;/code&gt; module&lt;/a&gt; is your friend. Use &lt;a href=&quot;https://nodejs.org/api/path.html#path_path_join_paths&quot;&gt;&lt;code&gt;path.join&lt;/code&gt;&lt;/a&gt; whenever you have to join two paths. And check out other utilities like &lt;a href=&quot;https://nodejs.org/api/path.html#path_path_relative_from_to&quot;&gt;&lt;code&gt;path.relative&lt;/code&gt;&lt;/a&gt;, which returns a relative path from one path to another, and &lt;a href=&quot;https://nodejs.org/api/path.html#path_path_normalize_path&quot;&gt;&lt;code&gt;path.normalize&lt;/code&gt;&lt;/a&gt;, which returns a path resolving segments like &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;..&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pay no attention to &lt;a href=&quot;https://nodejs.org/api/path.html#path_path_sep&quot;&gt;&lt;code&gt;path.sep&lt;/code&gt;&lt;/a&gt;, which returns a &lt;code&gt;/&lt;/code&gt; or a &lt;code&gt;\&lt;/code&gt; depending on the system you&apos;re working on, just use &lt;code&gt;path.join&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Paths behave differently to strings&lt;/h2&gt;
&lt;p&gt;To my second mistake, this time working in Ruby. This one was slightly more subtle and evaded my tests. You see, you can use the &lt;code&gt;Pathname&lt;/code&gt; class to create fragments of paths and then concatenate them. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;pathname&quot;
path1 = Pathname.new(&quot;path&quot;)
path2 = Pathname.new(&quot;to&quot;)
path1 + path2
# =&amp;gt; #&amp;lt;Pathname:path/to&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see &lt;code&gt;Pathname&lt;/code&gt; objects have a &lt;code&gt;+&lt;/code&gt; operator that concatenates the paths, much like &lt;code&gt;+&lt;/code&gt; concatenates strings. In fact, it also works with a mix of strings and paths:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;pathname&quot;
path1 = Pathname.new(&quot;path&quot;)
path2 = &quot;to&quot;
path1 + path2
# =&amp;gt; #&amp;lt;Pathname:path/to&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This all seems well and good, except it doesn&apos;t work the other way around.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;pathname&quot;
path1 = &quot;to&quot;
path2 = Pathname.new(&quot;path&quot;)
path1 + path2
# =&amp;gt; TypeError (no implicit conversion of Pathname into String)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A nice error like that means we&apos;ve done something wrong, that was not the problem I had though. No, the issue I had stemmed from expecting to concatenate a pathname and a string and instead concatenating two strings. This manifested itself in &lt;a href=&quot;https://github.com/philnash/jekyll-gzip/&quot;&gt;my Rubygem &lt;code&gt;jekyll-gzip&lt;/code&gt;&lt;/a&gt;. You see, I was trying to create a glob of paths with the line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;files = Dir.glob(dir + &quot;**/*{#{extensions}}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turned out under some circumstances &lt;code&gt;dir&lt;/code&gt; was actually a string instead of a pathname and it didn&apos;t include a separator. So the glob was looking for &lt;code&gt;&quot;dirname**/*{#{extensions}}&quot;&lt;/code&gt; when I really wanted it to look for &lt;code&gt;&quot;dirname/**/*{#{extensions}}&quot;&lt;/code&gt;. Concatenating two pathnames or a pathname and a string will add the separator (&lt;a href=&quot;https://github.com/philnash/jekyll-gzip/commit/6651b7f51b62cd14a3e256d77fa604a49eacb9d8#diff-392aaa6a279f62e98df890fff8d82d1eL54-R54&quot;&gt;as someone pointed out in a comment on my commit&lt;/a&gt;), but concatenating two strings will not. This meant that the gem happily went looking for the wrong pathname, found no files and then proceeded to successfully do nothing. Replacing the entire line with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;files = Dir.glob(File.join(dir, &quot;**&quot;, &quot;*{#{extensions}}&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fixed the issue. In this case &lt;a href=&quot;https://ruby-doc.org/core-2.6.2/File.html#method-c-join&quot;&gt;&lt;code&gt;File.join&lt;/code&gt;&lt;/a&gt; is the method to use to avoid surprises with strings.&lt;/p&gt;
&lt;h2&gt;Always use the built in path tools&lt;/h2&gt;
&lt;p&gt;Whether you&apos;re working in Node.js, Ruby, or any other language do not be tempted to treat file paths as strings. They behave differently on different platforms and mixing paths and strings together can cause hard to debug errors.&lt;/p&gt;
&lt;p&gt;Use your standard library and save yourself the hassle.&lt;/p&gt;
</description><pubDate>Wed, 04 Mar 2020 00:00:00 GMT</pubDate><category>ruby</category><category>node</category><category>strings</category></item><item><title>How to find CFPs for developer conferences</title><link>https://philna.sh/blog/2020/01/29/how-to-find-cfps-for-developer-conferences/</link><guid isPermaLink="true">https://philna.sh/blog/2020/01/29/how-to-find-cfps-for-developer-conferences/</guid><description>&lt;p&gt;So you&apos;ve decided to speak at a developer conference? You have a story you want to share with your peers—how you built something, how you learned something new, how you became a better developer and how everyone else can too—but you need to find a stage on which to share this story.&lt;/p&gt;
&lt;p&gt;There are hundreds of developer conferences out there, covering everything you could think of in our industry. Knowing they are there is nice, but finding out which are coming up and are have an open call for proposals (CFP) is the challenge. Over a number of years of submitting to many and &lt;a href=&quot;/speaking&quot;&gt;getting accepted to a few conferences&lt;/a&gt;, I have researched a number of resources for finding CFPs and I want to share the current crop in this post.&lt;/p&gt;
&lt;h2&gt;Websites, mailing lists, Twitter accounts&lt;/h2&gt;
&lt;p&gt;Ideally all CFPs that were relevant to your interests or communities would be sent directly to you. Sadly that&apos;s not how the world works. Instead, we are fortunate that a number of people and communities do the hard work to go out and find the available CFPs and aggregate them for us. The following list includes websites, mailing lists and Twitter accounts that share developer conferences and their CFPs. Mailing lists bring the CFPs to your inbox, Twitter accounts are great if you&apos;re all over social media and the sites are always worth browsing.&lt;/p&gt;
&lt;p&gt;Read on to discover how to find developer conference CFPs.&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/cfpland.png&quot; alt=&quot;CFP Land&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;57&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;Probably the most comprehensive website and mailing list full of developer CFPs that I know today, &lt;a href=&quot;https://www.cfpland.com/conferences/&quot;&gt;CFP Land has a great list of conference CFPs&lt;/a&gt; that you can filter by category, region, date and whether they offer hotel, travel or a stipend for speakers. CFP Land also sends out a round up of CFPs weekly and you can &lt;a href=&quot;https://www.cfpland.com/&quot;&gt;sign up for the mailing list on the CFP Land home page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a bonus it also has &lt;a href=&quot;https://twitter.com/cfp_land&quot;&gt;a Twitter account&lt;/a&gt;, &lt;a href=&quot;https://feeds.cfpland.com/v2/rss/cfps&quot;&gt;RSS feed&lt;/a&gt; and even a &lt;a href=&quot;https://cfpland.github.io/api-docs/&quot;&gt;CFP API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing to consider is that &lt;a href=&quot;https://pro.cfpland.com/&quot;&gt;CFP Land offers a professional account&lt;/a&gt;. The basic website and the other services listed above list CFPs that are closing within 21 days. The professional account gives you earlier access to the CFPs seen on the public site as well as tools to save CFPs you&apos;re interested in and reminders for when the deadlines are approaching. While I am not currently a customer and I can&apos;t comment on how much more useful this is, it is something I have considered and mostly I appreciate the effort to make CFP Land sustainable for the future.&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/techdailycfp.png&quot; alt=&quot;Tech Daily CFP&quot; loading=&quot;lazy&quot; width=&quot;137&quot; height=&quot;100&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;Tech Daily CFP is a &lt;a href=&quot;https://twitter.com/techdailycfp&quot;&gt;Twitter account&lt;/a&gt; that retweets developer CFPs and a &lt;a href=&quot;http://techdailycfp.com&quot;&gt;mailing list&lt;/a&gt; that gathers those retweets and mails a collection of them once a day.&lt;/p&gt;
&lt;p&gt;It can be quite intense to receive emails once a day with CFPs, but it definitely feels like you&apos;re not missing out on anything.&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/papercall.png&quot; alt=&quot;PaperCall&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;81&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.papercall.io/&quot;&gt;PaperCall&lt;/a&gt; is a site on which you can host your conference&apos;s CFP, so naturally it has a &lt;a href=&quot;https://www.papercall.io/events&quot;&gt;list of currently open CFPs&lt;/a&gt; that is always worth perusing. PaperCall used to run the very comprehensive WeeklyCFP mailing list, but I haven&apos;t heard from it for a while so I believe it&apos;s defunct (which is certainly a reason to support CFP Land&apos;s professional accounts).&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/confstech.png&quot; alt=&quot;Confs.tech&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;75&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://confs.tech/&quot;&gt;Confs.tech&lt;/a&gt; is a community sourced list of conferences. They also &lt;a href=&quot;https://confs.tech/cfp&quot;&gt;list live CFPs&lt;/a&gt; that you can filter by category and country. They &lt;a href=&quot;https://twitter.com/ConfsTech&quot;&gt;tweet out all the new conferences added to the site&lt;/a&gt; and I just discovered you can &lt;a href=&quot;https://confs.tech/&quot;&gt;sign up to their mailing list on their home page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/seecfp.png&quot; alt=&quot;SeeCFP&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;101&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;I came across SeeCFP on the day of writing this post, so I&apos;m not very well acquainted with it yet. It has a &lt;a href=&quot;https://airtable.com/shrBMFY4CSpSRGmAs&quot;&gt;list of CFPs stored in Airtable&lt;/a&gt; and a &lt;a href=&quot;https://seecfp.com/&quot;&gt;weekly mailing list you can sign up to on the home page&lt;/a&gt;. They also &lt;a href=&quot;https://twitter.com/appcfp&quot;&gt;tweet out CFPs that are close to their deadlines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/colloq.png&quot; alt=&quot;Colloq&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;105&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://colloq.io/&quot;&gt;Colloq&lt;/a&gt; is more than just a list of CFPs, it&apos;s a social event platform. It lists loads of events, including &lt;a href=&quot;https://colloq.io/events?callforpapers=open&quot;&gt;events with open CFPs&lt;/a&gt; but also lets you publicly say you are attending, links to ticket sales and collects coverage of the event after it happens (check out &lt;a href=&quot;https://colloq.io/events/accessibility-club/2019/berlin/1&quot;&gt;Accessibility Club Summit 2019&lt;/a&gt; for a good example of an event on Colloq).&lt;/p&gt;
&lt;p&gt;Colloq is also a good example of an application that has been built with privacy and security at its heart (if only I could say that about more applications). Just read their &lt;a href=&quot;https://colloq.io/blog/how-our-password-check-works&quot;&gt;blog post on how they keep user passwords secure&lt;/a&gt; to see the level of attention.&lt;/p&gt;
&lt;p&gt;&amp;lt;h3&amp;gt;
&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/cfps/tulula.png&quot; alt=&quot;Tulula&quot; loading=&quot;lazy&quot; width=&quot;122&quot; height=&quot;45&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/h3&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tulu.la/&quot;&gt;Tulula&lt;/a&gt; lists events, &lt;a href=&quot;https://tulu.la/call-for-papers/&quot;&gt;particularly those with open CFPs&lt;/a&gt; and has a comprehensive filtering system to find the events that are important to you. It also includes &lt;a href=&quot;https://forum.tulu.la/&quot;&gt;a forum&lt;/a&gt; and &lt;a href=&quot;https://tulu.la/slack/&quot;&gt;Slack community&lt;/a&gt; so that you can meet and chat with others trying to improve their public speaking too.&lt;/p&gt;
&lt;h3&gt;Other lists of conferences&lt;/h3&gt;
&lt;p&gt;There are a bunch more sites or Twitter accounts that are less comprehensive or that I don&apos;t necessarily use but came across as part of my research. Some are specific to languages or regions and any of them might be useful to browse once in a while.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://rubyconferences.org/&quot;&gt;Ruby Conferences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.smashingmagazine.com/web-tech-front-end-ux-conferences/&quot;&gt;Smashing Magazine&apos;s upcoming web design conferences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://conferences.css-tricks.com/&quot;&gt;CSS Tricks&apos; front-end design and development conferences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://callingallpapers.com/&quot;&gt;CallingAllPapers&lt;/a&gt; (also &lt;a href=&quot;https://twitter.com/callingallpaper&quot;&gt;on Twitter&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Readify/DevEvents&quot;&gt;Dev Events in Australia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cfp-organizer.herokuapp.com/&quot;&gt;CFP Organizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/benmvp/frontend-confs&quot;&gt;Frontend Conferences for Speaking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://joind.in/event&quot;&gt;Joindin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/phpcfps&quot;&gt;PHP CFPs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/moztechcfps&quot;&gt;Mozilla Tech CFPs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Good luck!&lt;/h2&gt;
&lt;p&gt;Finding the right conference for your talk is a challenge and hopefully these resources can help you to discover a few more opportunities.&lt;/p&gt;
&lt;p&gt;If I&apos;ve missed a CFP resource, please let me know &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;on Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next up, watch out for my new mailing list sharing sites that list CFPs 😉.&lt;/p&gt;
</description><pubDate>Wed, 29 Jan 2020 00:00:00 GMT</pubDate><category>speaking</category><category>devrel</category><category>conferences</category></item><item><title>Testing signed and encrypted cookies in Rails</title><link>https://philna.sh/blog/2020/01/15/test-signed-cookies-in-rails/</link><guid isPermaLink="true">https://philna.sh/blog/2020/01/15/test-signed-cookies-in-rails/</guid><description>&lt;p&gt;Recently I&apos;ve been refactoring the tests for &lt;a href=&quot;https://github.com/twilio/authy-devise&quot;&gt;a gem I maintain&lt;/a&gt; and I needed to test that it sets the right cookies at the right time. But the cookies in use in the gem are signed cookies and that caused a slight hiccup for me. I&apos;d never tested the value in a signed cookie before and it wasn&apos;t immediately obvious what to do.&lt;/p&gt;
&lt;p&gt;So I thought I would share what I found out in case it helps.&lt;/p&gt;
&lt;h2&gt;Cookies on Rails&lt;/h2&gt;
&lt;p&gt;In Rails applications there are three flavours of cookies available: simple session cookies, signed cookies and encrypted cookies. You can set any of these by using the &lt;code&gt;cookies&lt;/code&gt; object in a controller, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CookiesController &amp;lt;  ApplicationController
  def index
    cookies[&quot;simple&quot;] = &quot;Hello, I am easy to read.&quot;
    cookies.signed[&quot;protected&quot;] = &quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;
    cookies.encrypted[&quot;private&quot;] = &quot;Hello, I can&apos;t be read or tampered with.&quot;
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Simple cookies&lt;/h3&gt;
&lt;p&gt;Simple cookies are made up of plain text. If you inspected the cookie above called &quot;simple&quot; in the browser you would see the text &quot;Hello, I am easy to read.&quot;&lt;/p&gt;
&lt;p&gt;Simple cookies are ok for storing data that doesn&apos;t really matter. The end user can read and change it and your application shouldn&apos;t be affected.&lt;/p&gt;
&lt;h3&gt;Signed cookies&lt;/h3&gt;
&lt;p&gt;Signed cookies are not sent to the browser as plain text. Instead they comprise of a payload and signature separated by two dashes &lt;code&gt;--&lt;/code&gt;. Before the dashes, the payload is &lt;a href=&quot;https://en.wikipedia.org/wiki/Base64&quot;&gt;base 64 encoded data&lt;/a&gt;. To read the data you can base 64 decode it. This data isn&apos;t secret, but it can&apos;t be tampered with because the second part of the cookie is a signature. The signature is created by taking an HMAC SHA1 digest of the application&apos;s &lt;code&gt;secret_key_base&lt;/code&gt; and the data in the cookie. If the contents of the cookie are changed when you try to read the cookie, the signature will no longer match the contents and Rails will return &lt;code&gt;nil&lt;/code&gt;. Under the hood this is all handled by the &lt;a href=&quot;https://api.rubyonrails.org/v6.0.2.1/classes/ActiveSupport/MessageVerifier.html&quot;&gt;&lt;code&gt;ActiveSupport::MessageVerifier&lt;/code&gt;&lt;/a&gt;. As you can see above, you don&apos;t need to worry about that, you can treat the &lt;code&gt;cookies.signed&lt;/code&gt; object as if it were a hash.&lt;/p&gt;
&lt;p&gt;Signed cookies are useful for data that can be read by the user, but you need to trust is the same when you get it back to the server again.&lt;/p&gt;
&lt;h3&gt;Encrypted cookies&lt;/h3&gt;
&lt;p&gt;Encrypted cookies take this one step further and encrypt the data in the cookie, then sign it. This is handled by the &lt;a href=&quot;https://api.rubyonrails.org/v6.0.2.1/classes/ActiveSupport/MessageEncryptor.html&quot;&gt;&lt;code&gt;ActiveSupport::MessageEncryptor&lt;/code&gt;&lt;/a&gt; and means that without the &lt;code&gt;secret_key_base&lt;/code&gt; you cannot read or write to this cookie. Thankfully there&apos;s no need to worry about the encryption yourself, using the &lt;code&gt;cookies.encrypted&lt;/code&gt; object you can set encrypted cookies as though they were a regular hash.&lt;/p&gt;
&lt;p&gt;Encrypted cookies are useful for private data that you want to store with the user, but you don&apos;t want them, or anyone, to read.&lt;/p&gt;
&lt;h2&gt;Testing cookies&lt;/h2&gt;
&lt;p&gt;Suppose we now want to test the controller we saw above. We want to ensure that all of our cookies are set correctly. The test might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CookiesControllerTest &amp;lt; ActionDispatch::IntegrationTest
  test &quot;should set cookies when getting the index&quot; do
    get root_url
    assert_response :success
    assert_equal &quot;Hello, I am easy to read.&quot;, cookies[&quot;simple&quot;]
    assert_equal &quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;, cookies[&quot;protected&quot;]
    assert_equal &quot;Hello, I can&apos;t be read or tampered with.&quot;, cookies[&quot;private&quot;]
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or with RSpec Rails:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RSpec.describe CookiesController, type: :request do
  it &quot;should set cookies when getting the index&quot; do
    get root_url
    expect(response).to have_http_status(:success)
    expect(cookies[&quot;simple&quot;]).to eq(&quot;Hello, I am easy to read.&quot;)
    expect(cookies[&quot;protected&quot;]).to eq(&quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;)
    expect(cookies[&quot;private&quot;]).to eq(&quot;Hello, I can&apos;t be read or tampered with.&quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this would fail at the test for the signed cookie and wouldn&apos;t pass for the encrypted cookie either. You can&apos;t just call on those cookies straight out of the jar if they have been signed or encrypted.&lt;/p&gt;
&lt;p&gt;You might think you should test against the &lt;code&gt;signed&lt;/code&gt; and &lt;code&gt;encrypted&lt;/code&gt; version of the cookies, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    assert_equal &quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;, cookies.signed[&quot;protected&quot;]
    assert_equal &quot;Hello, I can&apos;t be read or tampered with.&quot;, cookies.encrypted[&quot;private&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That doesn&apos;t work either. At least it doesn&apos;t work if you are using the currently recommended way of testing controllers, with &lt;code&gt;ActionDispatch::IntegrationTest&lt;/code&gt; in Minitest or &lt;code&gt;type: :request&lt;/code&gt; in RSpec.&lt;/p&gt;
&lt;p&gt;If you have the older style &lt;code&gt;ActionController::TestCase&lt;/code&gt; or &lt;code&gt;type: :controller&lt;/code&gt; tests, then &lt;code&gt;cookies.signed&lt;/code&gt; and &lt;code&gt;cookies.encrypted&lt;/code&gt; will work. If you have an application with the older style tests, do carry on reading just in case you decide to refactor them to come in line with the current Rails way.&lt;/p&gt;
&lt;p&gt;With the tests above, the &lt;code&gt;cookies&lt;/code&gt; object is actually an instance of &lt;code&gt;Rack::Test::CookieJar&lt;/code&gt;, which does not have knowledge of your Rails application secrets.&lt;/p&gt;
&lt;h2&gt;So how do we test these cookies?&lt;/h2&gt;
&lt;p&gt;This is where I got to with the gem I was working on. I needed to test the result of a signed cookie, but I had a &lt;code&gt;Rack::Test::CookieJar&lt;/code&gt; object. The good news is we can bring the Rails application&apos;s own &lt;code&gt;ActionDispatch::Cookies::CookieJar&lt;/code&gt; back into play to decode your signed cookies and decrypt your encrypted cookies.&lt;/p&gt;
&lt;p&gt;To do so, you instantiate an instance of &lt;code&gt;ActionDispatch::Cookies::CookieJar&lt;/code&gt; using the &lt;code&gt;request&lt;/code&gt; object from the test and a hash of your cookie data. You can then call &lt;code&gt;signed&lt;/code&gt; or &lt;code&gt;encrypted&lt;/code&gt; on that cookie jar. So now the test looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CookiesControllerTest &amp;lt; ActionDispatch::IntegrationTest
  test &quot;should set cookies when getting the index&quot; do
    get root_url
    assert_response :success
    assert_equal &quot;Hello, I am easy to read.&quot;, cookies[&quot;simple&quot;]
    jar = ActionDispatch::Cookies::CookieJar.build(request, cookies.to_hash)
    assert_equal &quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;, jar.signed[&quot;protected&quot;]
    assert_equal &quot;Hello, I can&apos;t be read or tampered with.&quot;, jar.encrypted[&quot;private&quot;]
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the spec would look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RSpec.describe CookiesController, type: :request do
  it &quot;gets cookies from the response&quot; do
    get root_url
    expect(response).to have_http_status(:success)
    expect(cookies[&quot;simple&quot;]).to eq(&quot;Hello, I am easy to read.&quot;)
    jar = ActionDispatch::Cookies::CookieJar.build(request, cookies.to_hash)
    expect(jar.signed[&quot;protected&quot;]).to eq(&quot;Hello, I can be read, but I can&apos;t be tampered with.&quot;)
    expect(jar.encrypted[&quot;private&quot;]).to eq(&quot;Hello, I can&apos;t be read or tampered with.&quot;)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Red, Green, Re-snack-tor&lt;/h2&gt;
&lt;p&gt;In this post we&apos;ve seen how to test signed or encrypted cookies in Rails. Hopefully your test suite is running green and your cookies are covered now.&lt;/p&gt;
&lt;p&gt;I&apos;m going to get back to the refactor I was working on. There are plenty more tests to cover now that these cookies have been polished off.&lt;/p&gt;
</description><pubDate>Wed, 15 Jan 2020 00:00:00 GMT</pubDate><category>ruby</category><category>rails</category><category>testing</category></item><item><title>How not to sort an array in JavaScript</title><link>https://philna.sh/blog/2019/08/26/how-not-to-sort-an-array-in-javascript/</link><guid isPermaLink="true">https://philna.sh/blog/2019/08/26/how-not-to-sort-an-array-in-javascript/</guid><description>&lt;p&gt;Array sorting is one of those things you don&apos;t spend too long thinking about, until it stops working for you. Recently I was working with array of items in JavaScript that were not sorting at all properly and completely messing up an interface. It took me way too long to work out what went wrong so I wanted to share what happened and why it was so weird.&lt;/p&gt;
&lt;h2&gt;Basic sorting&lt;/h2&gt;
&lt;p&gt;JavaScript has a &lt;code&gt;sort&lt;/code&gt; method available on Array objects and running it will probably do what you expect. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const stringArray = [&apos;cat&apos;, &apos;dog&apos;, &apos;ant&apos;, &apos;butterfly&apos;];
stringArray.sort();
// =&amp;gt; [ &apos;ant&apos;, &apos;butterfly&apos;, &apos;cat&apos;, &apos;dog&apos; ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s even pretty good if you&apos;re sorting arrays that might have members that are &lt;code&gt;undefined&lt;/code&gt;. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort&quot;&gt;MDN says that &quot;all undefined elements are sorted to the end of the array.&quot;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const stringArrayWithUndefined = [
  &apos;cat&apos;,
  undefined,
  &apos;dog&apos;,
  undefined,
  &apos;ant&apos;,
  &apos;butterfly&apos;,
  &apos;zebra&apos;
];
stringArrayWithUndefined.sort();
// =&amp;gt; [ &apos;ant&apos;, &apos;butterfly&apos;, &apos;cat&apos;, &apos;dog&apos;, &apos;zebra&apos;, undefined, undefined ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gotchas&lt;/h2&gt;
&lt;p&gt;The first issue you might come across is if you find yourself with an array containing &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const stringArrayWithUndefinedAndNull = [
  &apos;cat&apos;,
  undefined,
  &apos;dog&apos;,
  undefined,
  &apos;ant&apos;,
  null,
  &apos;butterfly&apos;,
  &apos;zebra&apos;
];
stringArrayWithUndefinedAndNull.sort();
// =&amp;gt; [ &apos;ant&apos;, &apos;butterfly&apos;, &apos;cat&apos;, &apos;dog&apos;, null, &apos;zebra&apos;, undefined, undefined ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sorting will coerce the &lt;code&gt;null&lt;/code&gt; to the string &lt;code&gt;&quot;null&quot;&lt;/code&gt; which will appear somewhere in the middle of the alphabet.&lt;/p&gt;
&lt;p&gt;Then there are numbers. The default JavaScript sorting algorithm is to convert all members of an array to strings and then compare their sequences of UTF-16 code unit values. This works great for arrays of strings as we&apos;ve already seen, but it breaks down very quickly for numbers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numberArray = [5, 3, 7, 1];
numberArray.sort();
// =&amp;gt; [ 1, 3, 5, 7 ]

const biggerNumberArray = [5, 3, 10, 7, 1];
biggerNumberArray.sort();
// =&amp;gt; [ 1, 10, 3, 5, 7 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the example above, 10 gets sorted to before 3 because &quot;10&quot; is sorted before &quot;3&quot;.&lt;/p&gt;
&lt;p&gt;We can fix this by providing JavaScript a comparison function to use to perform the sort. The function receives two items from the array and it needs to return a numeric value and whether that value is above, below or equal to zero defines how the items are sorted relative to each other. If the return value is less than zero, then the first item is sorted in front of the second, if the value is above zero then the second item is sorted in front of the first. If the return value is 0 then the items stay in the same order with respect to each other.&lt;/p&gt;
&lt;p&gt;To sort numbers in ascending order, the comparison function is relatively simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const compareNumbers = (a, b) =&amp;gt; a - b;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Subtracting the first item from the second one satisfies the requirements above. Using this comparison function with our &lt;code&gt;biggerNumberArray&lt;/code&gt; from earlier will sort the numbers correctly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;biggerNumberArray.sort(compareNumbers);
// =&amp;gt; [ 1, 3, 5, 7, 10 ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This still works if you have &lt;code&gt;undefined&lt;/code&gt; elements as they are ignored and sorted to the end.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numberArrayWithUndefined = [5, undefined, 3, 10, 7, 1];
numberArrayWithUndefined.sort(compareNumbers);
// =&amp;gt; [ 1, 3, 5, 7, 10, undefined ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; causes problems again though.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numberArrayWithUndefinedAndNull = [5, undefined, 3, null, 10, 7, 1];
numberArrayWithUndefinedAndNull.sort(compareNumbers);
// =&amp;gt; [ null, 1, 3, 5, 7, 10, undefined ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This happens because coercing &lt;code&gt;null&lt;/code&gt; to a number returns 0.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Number(null);
// =&amp;gt; 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could handle this in your &lt;code&gt;compareNumbers&lt;/code&gt; function or be happy that it is consistent.&lt;/p&gt;
&lt;h2&gt;Inconsistent gotchas&lt;/h2&gt;
&lt;p&gt;The biggest problem, and this caught me out recently, comes when &lt;code&gt;undefined&lt;/code&gt; sneaks in another way. As we&apos;ve seen, if the array contains &lt;code&gt;undefined&lt;/code&gt; it&apos;s ignored and just sorted to the back. However, if you are sorting objects where the keys may be &lt;code&gt;undefined&lt;/code&gt; this automatic sorting doesn&apos;t happen and the results become inconsistent.&lt;/p&gt;
&lt;p&gt;For example, if you have an array of objects where some of them have values and some don&apos;t, trying to sort by that value will not give you the result you want.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const objectArray = [
  { value: 1 },
  { value: 10 },
  {},
  { value: 5 },
  { value: 7 },
  { value: 3 }
];
const compareObjects = (a, b) =&amp;gt; a.value - b.value;
objectArray.sort(compareObjects);
// =&amp;gt; [ { value: 1 },
//      { value: 10 },
//      {},
//      { value: 3 },
//      { value: 5 },
//      { value: 7 } ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Subtracting a number from &lt;code&gt;undefined&lt;/code&gt; or subtracting &lt;code&gt;undefined&lt;/code&gt; from a number both return &lt;code&gt;NaN&lt;/code&gt; and since that doesn&apos;t lay on the scale of numbers that &lt;code&gt;sort&lt;/code&gt; needs from the comparison function the results end up a little strange. In this case, the item that caused the problem stays where it started in the array and the other objects are locally sorted.&lt;/p&gt;
&lt;p&gt;There are a few ways around this, but the important thing is knowing that it can happen. In my case when I came across this, I filtered out the items that didn&apos;t have a value as they weren&apos;t important until they did.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;objectArray
  .filter(obj =&amp;gt; typeof obj.value !== &apos;undefined&apos;)
  .sort(compareObjects);
// =&amp;gt; [ { value: 1 },
//      { value: 3 },
//      { value: 5 },
//      { value: 7 },
//      { value: 10 } ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Beware sorting&lt;/h2&gt;
&lt;p&gt;The upshot of all of this is that the &lt;code&gt;sort&lt;/code&gt; function is not as straightforward as it might seem. Strings work, numbers need some input and while &lt;code&gt;undefined&lt;/code&gt; is handled as a primitive you have to keep an eye on coercing &lt;code&gt;null&lt;/code&gt;s or &lt;code&gt;undefined&lt;/code&gt; object values.&lt;/p&gt;
&lt;p&gt;Have you come across problems sorting in JavaScript or other languages? I&apos;d love to hear your stories too, so give me a shout on &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;Twitter at @philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Mon, 26 Aug 2019 00:00:00 GMT</pubDate><category>javascript</category></item><item><title>How to start a Node.js project</title><link>https://philna.sh/blog/2019/01/10/how-to-start-a-node-js-project/</link><guid isPermaLink="true">https://philna.sh/blog/2019/01/10/how-to-start-a-node-js-project/</guid><description>&lt;p&gt;Sometimes I write blog posts to remind myself what I&apos;ve learned and sometimes I write them because someone else shares something and I want to remember that better. This post is one of the latter.&lt;/p&gt;
&lt;h2&gt;Starting a Node.js project&lt;/h2&gt;
&lt;p&gt;Usually when I start a new Node.js project I use &lt;code&gt;npm&lt;/code&gt; to generate my initial project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm&lt;/code&gt; then asks me some questions and builds a &lt;code&gt;package.json&lt;/code&gt; file for me. Then I start building the project.&lt;/p&gt;
&lt;p&gt;Later I inevitably copy and paste a &lt;code&gt;.gitignore&lt;/code&gt; file from GitHub&apos;s useful repo of &lt;code&gt;.gitignore&lt;/code&gt; templates. And if I remember I&apos;ll actually create a &lt;code&gt;LICENSE&lt;/code&gt; file with the open source license that I intended to use.&lt;/p&gt;
&lt;p&gt;This is not efficient.&lt;/p&gt;
&lt;p&gt;Then this week I saw &lt;a href=&quot;https://twitter.com/bitandbang&quot;&gt;Tierney Cyren&lt;/a&gt; tweet this:&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;How to start any new Node.js project:&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;$ npx license mit &amp;gt; LICENSE&amp;lt;br&amp;gt;$ npx gitignore node&amp;lt;br&amp;gt;$ npx covgen YOUR_EMAIL_ADDRESS&amp;lt;br&amp;gt;$ npm init -y&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;You&apos;re ready to start coding.&amp;lt;/p&amp;gt;— Tierney Cyren (@bitandbang) &amp;lt;a href=&quot;https://twitter.com/bitandbang/status/1082331715471925250?ref_src=twsrc%5Etfw&quot;&amp;gt;January 7, 2019&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;These four commands do everything that I was doing manually and more, setting up a project for success right from the start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npx license mit&lt;/code&gt; uses the &lt;a href=&quot;https://www.npmjs.com/package/license&quot;&gt;license package&lt;/a&gt; to download a license of choice, in this case the &lt;a href=&quot;https://opensource.org/licenses/MIT&quot;&gt;MIT license&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx gitignore node&lt;/code&gt; uses the &lt;a href=&quot;https://www.npmjs.com/package/gitignore&quot;&gt;gitignore package&lt;/a&gt; to automatically download the relevant &lt;code&gt;.gitignore&lt;/code&gt; file from &lt;a href=&quot;https://github.com/github/gitignore&quot;&gt;GitHub&apos;s repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;npx covgen&lt;/code&gt; uses the &lt;a href=&quot;https://www.npmjs.com/package/covgen&quot;&gt;covgen package&lt;/a&gt; to generate the &lt;a href=&quot;https://www.contributor-covenant.org/&quot;&gt;Contributor Covenant&lt;/a&gt; and give your project a code of conduct that will be welcoming to all contributors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;If you&apos;ve not seen &lt;a href=&quot;https://www.npmjs.com/package/npx&quot;&gt;&lt;code&gt;npx&lt;/code&gt;&lt;/a&gt; before it looks locally to see if there is a command to run and executes it, if there is no local command it will try to download, install the command from &lt;code&gt;npm&lt;/code&gt;, and run it. This is really useful when generating new projects and saves you from globally installing a bunch of &lt;code&gt;npm&lt;/code&gt; packages that are only used in this setup mode.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm init -y&lt;/code&gt; accepts all of the default options that &lt;code&gt;npm init&lt;/code&gt; asks you about&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tierney also suggested customising your &lt;code&gt;npm init&lt;/code&gt; defaults so that the output of &lt;code&gt;npm init -y&lt;/code&gt; is correct.&lt;/p&gt;
&lt;h2&gt;Customising &lt;code&gt;npm init&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;You can see your current &lt;code&gt;npm&lt;/code&gt; config by entering &lt;code&gt;npm config list&lt;/code&gt; on the command line. To just see the config that affects &lt;code&gt;npm init&lt;/code&gt; you can &lt;code&gt;grep&lt;/code&gt; for &quot;init&quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm config list | grep init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a number of defaults you can set; author name, author email, author url, the license, and the version. To set them, you can enter them on the command line or use &lt;code&gt;npm config edit&lt;/code&gt; to open up the config file in your text editor. The command line is easy enough though, you can set all five defaults like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm set init.author.name &quot;Your name&quot;
npm set init.author.email &quot;your@email.com&quot;
npm set init.author.url &quot;https://your-url.com&quot;
npm set init.license &quot;MIT&quot;
npm set init.version &quot;1.0.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you have that customised to your liking, &lt;code&gt;npm init -y&lt;/code&gt; will always produce the right settings.&lt;/p&gt;
&lt;h2&gt;Building your own init script&lt;/h2&gt;
&lt;p&gt;There are some improvements that I&apos;d make to Tierney&apos;s commands, though I appreciate they were constrained by Twitter. Here&apos;s a bash script I have come up with inspired by their tweet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function node-project {
  git init
  npx license $(npm get init.license) -o &quot;$(npm get init.author.name)&quot; &amp;gt; LICENSE
  npx gitignore node
  npx covgen &quot;$(npm get init.author.email)&quot;
  npm init -y
  git add -A
  git commit -m &quot;Initial commit&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To the original I&apos;ve added fetching the license type, the author name and email from the &lt;code&gt;npm init&lt;/code&gt; defaults. I&apos;ve also initialised a new git repository and committed the results of this script as the &quot;Initial commit&quot;.&lt;/p&gt;
&lt;p&gt;You can take this function and add it to your &lt;code&gt;~/.bash_profile&lt;/code&gt;. Then, either &lt;code&gt;source ~/.bash_profile&lt;/code&gt; or open a new command line window and run &lt;code&gt;node-project&lt;/code&gt;. Feel free to add or remove other bits as you see fit to create your perfect initialisation script.&lt;/p&gt;
&lt;h2&gt;Go start a project&lt;/h2&gt;
&lt;p&gt;Now you have the perfect script to start a Node.js project why not go create a new one? I have a few small projects in mind that I plan to build this year and this is a nice basis to start from.&lt;/p&gt;
&lt;p&gt;If you have any more suggestions to improve the script, let me know on &lt;a href=&quot;https://twitter.com&quot;&gt;Twitter at @philnash&lt;/a&gt;. Happy open sourcing!&lt;/p&gt;
</description><pubDate>Thu, 10 Jan 2019 00:00:00 GMT</pubDate><category>node</category><category>javascript</category><category>open source</category></item><item><title>2018 in review</title><link>https://philna.sh/blog/2019/01/03/2018-in-review/</link><guid isPermaLink="true">https://philna.sh/blog/2019/01/03/2018-in-review/</guid><description>&lt;p&gt;I&apos;ve never been one for writing a review of my year. This year I&apos;ve found myself not only reading, but comparing myself to others&apos; reviews. I realised that this is not particularly helpful, so decided I&apos;d give myself something to compare myself to at the end of 2019. Thus, here is my first year in review.&lt;/p&gt;
&lt;p&gt;Here are some of the things I was proud of this year:&lt;/p&gt;
&lt;h2&gt;Professional&lt;/h2&gt;
&lt;p&gt;July 2018 saw me cross 4 years of working as a developer evangelist for &lt;a href=&quot;http://twilio.com/&quot;&gt;Twilio&lt;/a&gt;. It&apos;s been a really fun, interesting, hard, tiring and compelling role over this time. I&apos;m almost half way through my 5th year now and I&apos;m having a great time sharing my development experiences, helping other developers and being a part of the Twilio community.&lt;/p&gt;
&lt;h2&gt;Development&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/philnash?tab=overview&amp;amp;from=2018-12-01&amp;amp;to=2018-12-31&quot;&gt;GitHub tells me I made 582 contributions in 2018&lt;/a&gt;. Those contributions were a good mix of personal projects, example Twilio applications and contributions to other open source projects. They also resulted in the release of 5 libraries; 4 in Ruby and one in JavaScript. They were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philnash/pwned&quot;&gt;pwned&lt;/a&gt;: a library to &lt;a href=&quot;https://www.twilio.com/blog/2018/03/better-passwords-in-ruby-applications-pwned-passwords-api.html&quot;&gt;access the Pwned Passwords API with Ruby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Three Ruby libraries for compressing text files in Jekyll sites:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philnash/jekyll-gzip&quot;&gt;jekyll-gzip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philnash/jekyll-zopfli&quot;&gt;jekyll-zopfli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philnash/jekyll-brotli/&quot;&gt;jekyll-brotli&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/philnash/web-share-wrapper&quot;&gt;web-share-wrapper&lt;/a&gt;: a web component &lt;a href=&quot;https://philna.sh/blog/2018/04/25/web-share-api-with-web-components/&quot;&gt;to replace share links with the web share API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also began work to resurrect my &lt;a href=&quot;https://github.com/philnash/bitly/&quot;&gt;RubyGem for the Bitly API&lt;/a&gt; as it has fallen way out of date. I need to spend more time with this though, the going has been slow so far.&lt;/p&gt;
&lt;p&gt;At work I took over maintenance of the &lt;a href=&quot;https://github.com/twilio/authy-devise&quot;&gt;Authy-Devise library&lt;/a&gt;. The library makes it easy to implement two factor authentication in &lt;a href=&quot;https://github.com/plataformatec/devise&quot;&gt;Devise&lt;/a&gt; with &lt;a href=&quot;https://www.twilio.com/docs/authy&quot;&gt;Twilio&apos;s Authy API&lt;/a&gt;. There&apos;s some good opportunities to improve the library this year, especially in testing. There are some interesting issues with testing a library that plugs in to a library that relies on Rails.&lt;/p&gt;
&lt;h3&gt;Helping&lt;/h3&gt;
&lt;p&gt;I like to help out &lt;a href=&quot;https://stackoverflow.com/users/28376/philnash?tab=profile&quot;&gt;on Stack Overflow&lt;/a&gt;. At the end of 2018 my reputation was 37,030 which I am incredibly proud of. In 2018 I left 345 answers, of which 169 were accepted. I also left 918 comments on questions/answers either asking for more information or following up.&lt;/p&gt;
&lt;p&gt;I am fortunate enough to do this as part of my work, as it can take up quite a bit of time. Most of my answers are for questions tagged &quot;Twilio&quot; though you&apos;d be surprised by the variety of problems developers have within the bounds of including Twilio in an application.&lt;/p&gt;
&lt;h3&gt;Learning&lt;/h3&gt;
&lt;p&gt;In &quot;exceptionally late to the party&quot; news I managed to take some time in 2018 to start learning both React and Angular. I&apos;ve never been a big fan of applications that just work in the front-end (unless absolutely necessary, you can&apos;t host a WebRTC video chat without JavaScript!) and frameworks have tended to encourage that. This has changed over time and frankly both React and Angular are too big to ignore any more (and arguably were well before 2018 too).&lt;/p&gt;
&lt;p&gt;I&apos;ve enjoyed kicking up my learning and there&apos;s going to be more of this, likely around React, for 2019. Look out for more blog posts as I discover new things.&lt;/p&gt;
&lt;h2&gt;Writing&lt;/h2&gt;
&lt;p&gt;This leads me to my writing this year. Before 2018 I wrote the last post on &lt;a href=&quot;/blog&quot;&gt;my blog&lt;/a&gt; in &lt;a href=&quot;/blog/2017/07/12/two-tests-you-should-run-against-your-ruby-project-now/&quot;&gt;July 2017&lt;/a&gt;. I intended to be more consistent in my writing in 2018 and that sort of worked for the first 3/4 of the year. My last posts on both my blog and the Twilio blog were both in October. Still, the results aren&apos;t all bad. I wrote:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;9 posts on &lt;a href=&quot;/blog&quot;&gt;philna.sh/blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;11 posts for &lt;a href=&quot;https://www.twilio.com/blog/author/pnash/&quot;&gt;Twilio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1 guest post for &lt;a href=&quot;https://rollbar.com/blog/top-10-ruby-on-rails-errors/&quot;&gt;Rollbar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;21 posts in total is good, but I want to improve on that this year. More consistency is the main aim again. Thankfully I have a number of drafts and projects already lined up.&lt;/p&gt;
&lt;h2&gt;Speaking&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/speaking/history/&quot;&gt;I spoke 12 times in 2018&lt;/a&gt;. This is a marked step down from 2017 and even further from 2016. This coincides with my move to Australia where there are fewer events and fewer nearby countries to pop over to give talks, something I was fortunate enough to do in the past.&lt;/p&gt;
&lt;p&gt;That said, speaking is more about quality than quantity. I was lucky to be selected to speak at these wonderful conferences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://2018.jsconf.eu/speakers/&quot;&gt;JSConf EU&lt;/a&gt; in Berlin&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.webdirections.org/code/speakers/phil-nash.php&quot;&gt;Web Directions Code&lt;/a&gt; in Melbourne&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ndcsydney.com/talk/service-workers-beyond-the-cache/&quot;&gt;NDC Sydney&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Voxxed Days Melbourne&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.angularconf.com.au/#speakers&quot;&gt;Angular Conf Australia&lt;/a&gt; (learning Angular this year paid off!)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gdgmelbourne.com/devfest-2018/&quot;&gt;Melbourne DevFest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JSConf EU was certainly my highlight here, I&apos;ve been trying for years to get a talk accepted and I was elated when I received the confirmation email. The talk was one of the more important ones I&apos;ve given too, addressing developers&apos; use of push notifications and permissions on the web. It might also be the best talk title I&apos;ve ever come up with. You can see &lt;a href=&quot;https://www.youtube.com/watch?v=uo-UOvq3-0Y&quot;&gt;&quot;Aggressive Web Apps&quot; on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had a lot of fun also presenting at local meetups. 2018 included talks at &lt;a href=&quot;http://melbjs.com/&quot;&gt;MelbJS&lt;/a&gt;, &lt;a href=&quot;https://www.meetup.com/the-web/&quot;&gt;The Web Meetup&lt;/a&gt;, &lt;a href=&quot;https://www.meetup.com/Ruby-On-Rails-Oceania-Melbourne/&quot;&gt;RoRo Melbourne&lt;/a&gt; and &lt;a href=&quot;https://www.meetup.com/Ruby-On-Rails-Oceania-Sydney/&quot;&gt;RoRo Sydney&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Travel&lt;/h2&gt;
&lt;p&gt;In 2018 I visited Sydney, Brisbane, Hobart, Singapore, Phuket, Bali, London, Berlin, New York, San Diego and San Francisco. 11 different places and 6 countries (aside from Australia).&lt;/p&gt;
&lt;p&gt;I certainly racked up some miles there. I enjoy travelling, it&apos;s both part of the job and how I like to holiday. It can be tiring, particularly when it is for work, but with enough recovery time built in this much travel is sustainable. Getting to travel back to Europe for JSConf EU and use that as an opportunity to call in on my friends and family in the UK was fantastic too.&lt;/p&gt;
&lt;p&gt;I am looking forward to exploring more new parts of the world in 2019.&lt;/p&gt;
&lt;h2&gt;Personal&lt;/h2&gt;
&lt;p&gt;2018 was my first full year living in Australia. It&apos;s been a fantastic year, Melbourne is an amazing city full of great food, interesting beer, lot&apos;s to do and above all welcoming and friendly people. &lt;a href=&quot;https://twitter.com/kellydunlop&quot;&gt;Kelly&lt;/a&gt; and I liked it so much we bought a house here this year! This, combined with getting my permanent residency, means that we&apos;re going to be enjoying life in Melbourne for a while longer.&lt;/p&gt;
&lt;h2&gt;In numbers&lt;/h2&gt;
&lt;p&gt;To summarise the above, here&apos;s what it looks like in numbers:&lt;/p&gt;
&lt;p&gt;🏠 1 new house&amp;lt;br&amp;gt;
🛂 1 permanent residency&amp;lt;br&amp;gt;
☎️ 4 years at Twilio&amp;lt;br&amp;gt;
📝 21 blog posts&amp;lt;br&amp;gt;
🎙 12 talks given&amp;lt;br&amp;gt;
💻 582 GitHub contributions&amp;lt;br&amp;gt;
💎 5 new libraries released&amp;lt;br&amp;gt;
🏙 11 cities visited&amp;lt;br&amp;gt;
🌏 6 countries visited&amp;lt;br&amp;gt;
🍻 &lt;a href=&quot;https://untappd.com/user/philnash/beers&quot;&gt;296 new beers&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I hope you had a great 2018 and wish you all the best for 2019.&lt;/p&gt;
</description><pubDate>Thu, 03 Jan 2019 00:00:00 GMT</pubDate><category>personal</category><category>yearinreview</category></item><item><title>Service workers: beware Safari&apos;s range request</title><link>https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/</link><guid isPermaLink="true">https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/</guid><description>&lt;p&gt;You&apos;ve implemented a service worker to cache some assets. Everything is working well, your service worker is a success, you&apos;re feeling good. But then...&lt;/p&gt;
&lt;p&gt;Some time passes and you deploy a video to your site. Everything is still working well in Chrome, in Firefox, in Edge. You check Safari. The video is broken. You don&apos;t know what&apos;s gone wrong.&lt;/p&gt;
&lt;p&gt;That was me last month. I published an article on &lt;a href=&quot;/blog/2018/09/27/techniques-for-animating-on-the-canvas-in-react/&quot;&gt;animating in the canvas with React&lt;/a&gt; and wanted to use an animation as the header image. &lt;a href=&quot;https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/replace-animated-gifs-with-video/&quot;&gt;A video was much smaller than using a comparable gif&lt;/a&gt; so I implemented the header as a video. When I came to check things in Safari I found it had stopped working and I had no idea why.&lt;/p&gt;
&lt;h2&gt;Diagnosing the problem&lt;/h2&gt;
&lt;p&gt;I first thought it could have something to do with the CDN I&apos;m using. There were some false positives regarding streaming video through a CDN that resulted in some extra research that was ultimately fruitless. Once I&apos;d exhausted that line of inquiry I went back to the failing request.&lt;/p&gt;
&lt;p&gt;Observing the request in Safari&apos;s inspector lead to further trawling the internet and eventually things started to add up. Safari was sending an initial request to fetch the video with a &lt;code&gt;Range&lt;/code&gt; header set to &lt;code&gt;bytes=0-1&lt;/code&gt;. You see, &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/CreatingVideoforSafarioniPhone/CreatingVideoforSafarioniPhone.html#//apple_ref/doc/uid/TP40006514-SW6&quot;&gt;Safari requires HTTP servers that are serving video and audio to support &lt;code&gt;Range&lt;/code&gt; requests like this&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nginx was serving correct responses to &lt;code&gt;Range&lt;/code&gt; requests. So was the CDN. The only other problem? The service worker. And this broke the video in Safari.&lt;/p&gt;
&lt;p&gt;Time to fix the service worker then. With inspiration from this &lt;a href=&quot;https://googlechrome.github.io/samples/service-worker/prefetch-video/&quot;&gt;example of caching videos from the Chrome team&lt;/a&gt;, here&apos;s what I did.&lt;/p&gt;
&lt;h2&gt;Range requests in Service Workers&lt;/h2&gt;
&lt;p&gt;My existing service worker implementation was checking for requests to my assets or images directories and responding with a cache then network strategy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;self.addEventListener(&apos;fetch&apos;, function(event) {
  var url = new URL(event.request.url);
  if (url.pathname.match(/^\/((assets|images)\/|manifest.json$)/)) {
    event.respondWith(returnFromCacheOrFetch(event.request, staticCacheName));
  }
  // other strategies
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The video requests are going to be served from the assets or image directories, so this is the place to interject. I checked for the existence of the range header and responded with a different approach.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;self.addEventListener(&apos;fetch&apos;, function(event) {
  var url = new URL(event.request.url);
  if (url.pathname.match(/^\/((assets|images)\/|manifest.json$)/)) {
    if (event.request.headers.get(&apos;range&apos;)) {
      event.respondWith(returnRangeRequest(event.request, staticCacheName));
    } else {
      event.respondWith(returnFromCacheOrFetch(event.request, staticCacheName));
    }
  }
  // other strategies
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, to implement &lt;code&gt;returnRangeRequest&lt;/code&gt;. This starts with a cache then network approach that you may have seen in a service worker before. The cache is opened and checked against the request. If there is a cached response it is returned and if it is not present in the cache it is fetched from the network, the response cloned and stored in the cache and the result returned.&lt;/p&gt;
&lt;p&gt;The important thing is that the result is turned into an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&quot;&gt;&lt;code&gt;ArrayBuffer&lt;/code&gt;&lt;/a&gt; using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Body/arrayBuffer&quot;&gt;&lt;code&gt;Response#arrayBuffer&lt;/code&gt;&lt;/a&gt;. This will give us access to the raw bytes to build our response from later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function returnRangeRequest(request, cacheName) {
  return caches
    .open(cacheName)
    .then(function(cache) {
      return cache.match(request.url);
    })
    .then(function(res) {
      if (!res) {
        return fetch(request)
          .then(res =&amp;gt; {
            const clonedRes = res.clone();
            return caches
              .open(cacheName)
              .then(cache =&amp;gt; cache.put(request, clonedRes))
              .then(() =&amp;gt; res);
          })
          .then(res =&amp;gt; {
            return res.arrayBuffer();
          });
      }
      return res.arrayBuffer();
    })
    .then(...); // The rest goes here
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we have an &lt;code&gt;arrayBuffer&lt;/code&gt;, from either the cache or network response, the real work starts. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range&quot;&gt;&lt;code&gt;Range&lt;/code&gt; header&lt;/a&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Range: bytes=200-1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the request is for the bytes between the 200th and 1000th byte of the response. The &lt;code&gt;Range&lt;/code&gt; header may omit the end byte, meaning that it wants all the bytes from the first value until the end of the file. We can extract these figures with a little regular expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  }).then(function(arrayBuffer) {
    const bytes = /^bytes\=(\d+)\-(\d+)?$/g.exec(
      request.headers.get(&apos;range&apos;)
    );
    // and so on
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking this regex down quickly, it looks for a string that starts with the &quot;bytes=&quot; followed by some digits a hyphen and optionally some more digits before the end of the string. The two sets of digits are captured, using the brackets, so that we can use them later.&lt;/p&gt;
&lt;p&gt;I check to see if the header satisfied the regex and if so, turn the start and end byte values into integers to index into the &lt;code&gt;arrayBuffer&lt;/code&gt;. If the end byte index is not present then it is set to the end of the file. With the byte indices the response can be generated.&lt;/p&gt;
&lt;p&gt;The body of the response is the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/slice&quot;&gt;slice&lt;/a&gt; of the &lt;code&gt;arrayBuffer&lt;/code&gt; from the start until the end defined by the &lt;code&gt;Range&lt;/code&gt; header. The response carries a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206&quot;&gt;206 status&lt;/a&gt;, meaning partial content. It also requires a &lt;code&gt;Content-Range&lt;/code&gt; header, which is similar to the original &lt;code&gt;Range&lt;/code&gt; header. It tells the browser the response is made of the range of bytes and also returns the total size of the file.&lt;/p&gt;
&lt;p&gt;If the regex fails then the service worker will return a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416&quot;&gt;416 error&lt;/a&gt; instead.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  }).then(function(arrayBuffer) {
    const bytes = /^bytes\=(\d+)\-(\d+)?$/g.exec(
      request.headers.get(&apos;range&apos;)
    );
    if (bytes) {
      const start = Number(bytes[1]);
      const end = Number(bytes[2]) || arrayBuffer.byteLength - 1;
      return new Response(arrayBuffer.slice(start, end + 1), {
        status: 206,
        statusText: &apos;Partial Content&apos;,
        headers: [
          [&apos;Content-Range&apos;, `bytes ${start}-${end}/${arrayBuffer.byteLength}`]
        ]
      });
    } else {
      return new Response(null, {
        status: 416,
        statusText: &apos;Range Not Satisfiable&apos;,
        headers: [[&apos;Content-Range&apos;, `*/${arrayBuffer.byteLength}`]]
      });
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That fixed it. With a reload of the service worker my video started playing in all the browsers again.&lt;/p&gt;
&lt;h2&gt;Service workers and browsers&lt;/h2&gt;
&lt;p&gt;You can see the &lt;a href=&quot;https://github.com/philnash/philna.sh/blob/ba798a2d5d8364fc7c1dae1819cbd8ef103c8b67/sw.js#L50-L94&quot;&gt;full function in my service worker on GitHub&lt;/a&gt;. It&apos;s quite a lot of extra work just to get Safari to agree to display video, but it did teach me a bit about what browsers expect when loading video content, how to respond to more intricate requests within a service worker, and how picky Safari can be.&lt;/p&gt;
&lt;p&gt;If you have this same problem, I hope my code helps. If you are using Workbox for a service worker it has a &lt;a href=&quot;https://developers.google.com/web/tools/workbox/modules/workbox-range-requests&quot;&gt;range request module&lt;/a&gt; that you can use for this too.&lt;/p&gt;
&lt;p&gt;If you discovered this problem yourself or if you have another solution for this, I&apos;d love to hear about it. Just drop me a note on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Tue, 23 Oct 2018 00:00:00 GMT</pubDate><category>javascript</category><category>service worker</category><category>web</category><category>pwa</category></item><item><title>Techniques for animating on the canvas in React</title><link>https://philna.sh/blog/2018/09/27/techniques-for-animating-on-the-canvas-in-react/</link><guid isPermaLink="true">https://philna.sh/blog/2018/09/27/techniques-for-animating-on-the-canvas-in-react/</guid><description>&lt;p&gt;I recently experimented with &lt;a href=&quot;https://www.twilio.com/blog/audio-visualisation-web-audio-api--react&quot;&gt;audio visualisation in React on the Twilio blog&lt;/a&gt;. While I meant to teach myself more about the web audio API I found that I picked up a few techniques for animating in canvas within a React project. If you&apos;re creating a canvas animation in React then perhaps this will help you too.&lt;/p&gt;
&lt;h2&gt;Good references&lt;/h2&gt;
&lt;p&gt;First up, if you&apos;ve used React before you&apos;ll know that you&apos;re supposed to avoid touching the &amp;lt;abbr title=&quot;Document Object Model&quot;&amp;gt;DOM&amp;lt;/abbr&amp;gt; and let React handle it. If you&apos;ve worked with an HTML5 &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; before, you&apos;ll also know that to get a context with which to draw on the canvas, you need to call directly on the canvas element itself. Thankfully this is an edge case that &lt;a href=&quot;https://reactjs.org/docs/refs-and-the-dom.html&quot;&gt;React supports through refs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get a ref to a canvas element inside a React component you first need to create the ref in the constructor using &lt;code&gt;React.createRef&lt;/code&gt;. When you come to render the canvas element, add a prop called &lt;code&gt;ref&lt;/code&gt; that points to the ref you created.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Animation extends React.Component {
  constructor(props) {
    super(props);
    this.canvasRef = React.createRef();
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;canvas ref={this.canvasRef} /&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you&apos;ve set it up this way, you can then refer to the canvas element through the ref&apos;s &lt;code&gt;current&lt;/code&gt; property, for example in &lt;code&gt;componentDidMount&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  componentDidMount() {
    const canvas = this.canvasRef.current;
    const context = canvas.getContext(&apos;2d&apos;);
    context.fillRect(0, 0, canvas.width, canvas.height);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you have the context you can draw and animate all you like.&lt;/p&gt;
&lt;h2&gt;Separating animation and drawing&lt;/h2&gt;
&lt;p&gt;A lot of building with React is about maintaining the state of the view. The first time I animated something on a canvas in React I held the state and the code to draw it in the same component. After browsing examples online, I came across &lt;a href=&quot;https://codepen.io/vasilly/pen/NRKyWL&quot;&gt;this rotating square on CodePen&lt;/a&gt;. What I really liked about this example was the way the state was separated from the drawing with the use of two components. The state of the drawing was then passed from the animating component to the drawing component through props.&lt;/p&gt;
&lt;p&gt;I recreated the original to show the separation.&lt;/p&gt;
&lt;p&gt;First you define a &lt;code&gt;Canvas&lt;/code&gt; component that draws an image using the props as parameters.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Canvas extends React.Component {
  constructor(props) {
    super(props);
    this.canvasRef = React.createRef();
  }

  componentDidUpdate() {
    // Draws a square in the middle of the canvas rotated
    // around the centre by this.props.angle
    const { angle } = this.props;
    const canvas = this.canvasRef.current;
    const ctx = canvas.getContext(&apos;2d&apos;);
    const width = canvas.width;
    const height = canvas.height;
    ctx.save();
    ctx.beginPath();
    ctx.clearRect(0, 0, width, height);
    ctx.translate(width / 2, height / 2);
    ctx.rotate((angle * Math.PI) / 180);
    ctx.fillStyle = &apos;#4397AC&apos;;
    ctx.fillRect(-width / 4, -height / 4, width / 2, height / 2);
    ctx.restore();
  }

  render() {
    return &amp;lt;canvas width=&quot;300&quot; height=&quot;300&quot; ref={this.canvasRef} /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you create an &lt;code&gt;Animation&lt;/code&gt; component that runs an animation loop using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame&quot;&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/a&gt;. Each time the animation loop runs you update the parameters of the animation in the state and let React render the &lt;code&gt;Canvas&lt;/code&gt; with the updated props.&lt;/p&gt;
&lt;p&gt;Don&apos;t forget to implement &lt;code&gt;componentWillUnmount&lt;/code&gt; to stop the &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Animation extends React.Component {
  constructor(props) {
    super(props);
    this.state = { angle: 0 };
    this.updateAnimationState = this.updateAnimationState.bind(this);
  }

  componentDidMount() {
    this.rAF = requestAnimationFrame(this.updateAnimationState);
  }

  updateAnimationState() {
    this.setState(prevState =&amp;gt; ({ angle: prevState.angle + 1 }));
    this.rAF = requestAnimationFrame(this.updateAnimationState);
  }

  componentWillUnmount() {
    cancelAnimationFrame(this.rAF);
  }

  render() {
    return &amp;lt;Canvas angle={this.state.angle} /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see &lt;a href=&quot;https://codepen.io/philnash/pen/QVeOrd&quot;&gt;this in action in this pen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Rerendering&lt;/h2&gt;
&lt;p&gt;A concern when animating or doing other intensive visual updates in React is rerendering child elements too often, causing &lt;a href=&quot;http://jankfree.org/&quot;&gt;jank&lt;/a&gt;. When we are drawing on the canvas we never want the canvas element itself to be rerendered. So what&apos;s the best way to hint to React that we don&apos;t want that to happen?&lt;/p&gt;
&lt;p&gt;You might be thinking of the &lt;a href=&quot;https://reactjs.org/docs/react-component.html#shouldcomponentupdate&quot;&gt;&lt;code&gt;shouldComponentUpdate&lt;/code&gt;&lt;/a&gt; lifecycle method. Returning &lt;code&gt;false&lt;/code&gt; from &lt;code&gt;shouldComponentUpdate&lt;/code&gt; will let React know that this component doesn&apos;t need to change. However, if we&apos;re using the pattern above, returning &lt;code&gt;false&lt;/code&gt; from &lt;code&gt;shouldComponentUpdate&lt;/code&gt; will skip running &lt;code&gt;componentDidUpdate&lt;/code&gt; and that&apos;s responsible for our drawing.&lt;/p&gt;
&lt;p&gt;I eventually came across &lt;a href=&quot;https://stackoverflow.com/a/49803151/28376&quot;&gt;this answer from Dan Abramov to a question on StackOverflow&lt;/a&gt;. We can create a &lt;code&gt;PureCanvas&lt;/code&gt; component that implements &lt;code&gt;shouldComponentUpdate&lt;/code&gt; and returns &lt;code&gt;false&lt;/code&gt; and use a &lt;a href=&quot;https://reactjs.org/docs/refs-and-the-dom.html#callback-refs&quot;&gt;callback ref&lt;/a&gt; to get the reference to the canvas element in a parent &lt;code&gt;Canvas&lt;/code&gt; component.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: in &lt;a href=&quot;https://stackoverflow.com/a/49803151/28376&quot;&gt;Dan&apos;s answer&lt;/a&gt; he says that using the pattern above should be ok and the following technique is likely only necessary if you have profiled your application and found it makes a difference.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Updating the example above, we split the &lt;code&gt;Canvas&lt;/code&gt; component into a &lt;code&gt;Canvas&lt;/code&gt; and a &lt;code&gt;PureCanvas&lt;/code&gt;. First, the &lt;code&gt;PureCanvas&lt;/code&gt; uses a callback ref and a callback provided through the props to return the canvas context to the parent component. It also renders the canvas element itself.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PureCanvas extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      &amp;lt;canvas
        width=&quot;300&quot;
        height=&quot;300&quot;
        ref={node =&amp;gt;
          node ? this.props.contextRef(node.getContext(&apos;2d&apos;)) : null
        }
      /&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the &lt;code&gt;Canvas&lt;/code&gt; component passes a callback function, &lt;code&gt;saveContext&lt;/code&gt;, as the &lt;code&gt;contextRef&lt;/code&gt; prop when rendering the &lt;code&gt;PureCanvas&lt;/code&gt;. When the function is called we save the context (and cache the canvas element&apos;s width and height). The rest of the differences from before are turning references to &lt;code&gt;ctx&lt;/code&gt; to &lt;code&gt;this.ctx&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Canvas extends React.Component {
  constructor(props) {
    super(props);
    this.saveContext = this.saveContext.bind(this);
  }

  saveContext(ctx) {
    this.ctx = ctx;
    this.width = this.ctx.canvas.width;
    this.height = this.ctx.canvas.height;
  }

  componentDidUpdate() {
    const { angle } = this.props;
    this.ctx.save();
    this.ctx.beginPath();
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.ctx.translate(this.width / 2, this.height / 2);
    this.ctx.rotate((angle * Math.PI) / 180);
    this.ctx.fillStyle = &apos;#4397AC&apos;;
    this.ctx.fillRect(
      -this.width / 4,
      -this.height / 4,
      this.width / 2,
      this.height / 2
    );
    this.ctx.restore();
  }

  render() {
    return &amp;lt;PureCanvas contextRef={this.saveContext} /&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even though it is not necessary, I find this separation between animation, drawing and rendering the canvas element itself quite pleasing. You can &lt;a href=&quot;https://codepen.io/philnash/pen/pxzVzw&quot;&gt;see this example in action on CodePen too&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Canvas vs React&lt;/h2&gt;
&lt;p&gt;It&apos;s been an interesting journey working with a canvas element within React. The way they work feels very different to each other, so getting them in sync wasn&apos;t necessarily straightforward. Hopefully if you have this problem then these techniques can help you.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in other animations in React, do please check out my article on &lt;a href=&quot;https://www.twilio.com/blog/audio-visualisation-web-audio-api--react&quot;&gt;audio visualisation in React&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have another way of working with canvas in React I&apos;d love to hear. Drop me a note on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 27 Sep 2018 00:00:00 GMT</pubDate><category>react</category><category>javascript</category><category>animation</category><category>canvas</category></item><item><title>Implementing one time passwords in Crystal</title><link>https://philna.sh/blog/2018/09/04/implementing-one-time-passwords-in-crystal/</link><guid isPermaLink="true">https://philna.sh/blog/2018/09/04/implementing-one-time-passwords-in-crystal/</guid><description>&lt;p&gt;&lt;a href=&quot;https://crystal-lang.org/&quot;&gt;Crystal&lt;/a&gt; is still a young language, there aren&apos;t a lot of libraries available yet. For some this could be offputting, but for others this is a chance to learn about a language and provide useful tools for those also starting to use it.&lt;/p&gt;
&lt;p&gt;I&apos;ve given a &lt;a href=&quot;/speaking/history/&quot;&gt;number of talks&lt;/a&gt; over time, some of which were about &lt;a href=&quot;https://www.youtube.com/watch?v=WipxMQjssRE&quot;&gt;two factor authentication&lt;/a&gt;. A while back I took the opportunity to improve my knowledge and implement the &lt;a href=&quot;https://en.wikipedia.org/wiki/HMAC-based_One-time_Password_algorithm&quot;&gt;one time password algorithm&lt;/a&gt; in Crystal.&lt;/p&gt;
&lt;p&gt;This lead me to build the &lt;a href=&quot;https://github.com/philnash/crotp&quot;&gt;CrOTP library for one time passwords in Crystal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to check out the code, most of the important implementation can be found in the &lt;a href=&quot;https://github.com/philnash/crotp/blob/master/src/crotp/otp.cr&quot;&gt;&lt;code&gt;CrOTP::OTP&lt;/code&gt; module&lt;/a&gt;. The &lt;a href=&quot;https://github.com/philnash/crotp/blob/master/src/crotp/hotp.cr&quot;&gt;&lt;code&gt;CrOTP::HOTP&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/philnash/crotp/blob/master/src/crotp/totp.cr&quot;&gt;&lt;code&gt;CrOTP::TOTP&lt;/code&gt;&lt;/a&gt; classes take care of the different ways the counter is treated.&lt;/p&gt;
&lt;h2&gt;Using CrOTP&lt;/h2&gt;
&lt;p&gt;To use the CrOTP library in your own Crystal project you need to add it to the dependencies in your &lt;code&gt;shard.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies:
  crotp:
    github: philnash/crotp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then install the dependencies with &lt;code&gt;shards install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now you can &lt;code&gt;require &quot;crotp&quot;&lt;/code&gt; and use it in your application. Most uses on the web of one time passwords are time based tokens for two factor authentication. Here&apos;s how to generate and verify time based OTPs.&lt;/p&gt;
&lt;h3&gt;Getting started&lt;/h3&gt;
&lt;p&gt;First require the library and generate a random string to use as the secret.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;crotp&quot;

secret = Random.new.hex(16)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then create an instance of the &lt;code&gt;CrOTP::TOTP&lt;/code&gt; class with that secret.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;totp = CrOTP::TOTP.new(secret)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sharing secrets&lt;/h3&gt;
&lt;p&gt;You can generate a URI that can be shared with authenticator apps like &lt;a href=&quot;https://authy.com/&quot;&gt;Authy&lt;/a&gt; or Google Authenticator. To do so call &lt;code&gt;authenticator_uri&lt;/code&gt; with the name of your app as the issuer and the user account&apos;s username as the user.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;totp.authenticator_uri(issuer: &quot;Test app&quot;, user: &quot;philnash@example.com&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turn the resulting URI into a QR code and users can scan it to add the account to their app.&lt;/p&gt;
&lt;h3&gt;Generating and verifying&lt;/h3&gt;
&lt;p&gt;Now you can use the &lt;code&gt;totp&lt;/code&gt; object to generate a new one time password that will be valid within the current 30 second period of time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;password = totp.generate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To verify a code, you can use the same object, calling &lt;code&gt;verify&lt;/code&gt; instead.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;totp.verify(password)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also allow a drift, to give users more time to enter their code. The drift is the number of periods (periods are 30 seconds long by default) that can pass after the token was generated.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;totp.verify(password, at: Time.now, allowed_drift: 2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are &lt;a href=&quot;https://github.com/philnash/crotp/blob/master/example/crotp.cr&quot;&gt;more examples in the project repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;There&apos;s more work to be done&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/philnash/crotp&quot;&gt;CrOTP&lt;/a&gt; does the basic work for generating and verifying one time passwords. Of course, there&apos;s more &lt;a href=&quot;https://github.com/philnash/crotp#todo&quot;&gt;to do on the project&lt;/a&gt; when I find the time.&lt;/p&gt;
&lt;p&gt;I&apos;d love to hear if this project is useful to you or if you&apos;re interested in helping implement the missing features. Let me know on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Tue, 04 Sep 2018 00:00:00 GMT</pubDate><category>crystal</category><category>two factor authentication</category><category>security</category></item><item><title>Git commands to keep a fork up to date</title><link>https://philna.sh/blog/2018/08/21/git-commands-to-keep-a-fork-up-to-date/</link><guid isPermaLink="true">https://philna.sh/blog/2018/08/21/git-commands-to-keep-a-fork-up-to-date/</guid><description>&lt;p&gt;I&apos;ve seen the following tweet about git making its way around Twitter recently:&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;I ❤️ Git. But honestly, it intimidated me for years. I thought I needed to understand all its powerful features to be productive. I found that&apos;s not the case. You can be quite productive in Git with around 6 commands: &amp;lt;br&amp;gt;&amp;lt;br&amp;gt;branch&amp;lt;br&amp;gt;checkout&amp;lt;br&amp;gt;add&amp;lt;br&amp;gt;commit&amp;lt;br&amp;gt;pull&amp;lt;br&amp;gt;push&amp;lt;/p&amp;gt;— Cory House 🏠 (@housecor) &amp;lt;a href=&quot;https://twitter.com/housecor/status/1031190760278970368?ref_src=twsrc%5Etfw&quot;&amp;gt;August 19, 2018&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;And it&apos;s true, you &lt;em&gt;can&lt;/em&gt; do most of your work with those commands.&lt;/p&gt;
&lt;p&gt;What if you want to fork and contribute to an open source project on GitHub, GitLab or BitBucket though? You&apos;re going to need a few more commands so that you can keep your fork up to date, namely &lt;code&gt;remote&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;merge&lt;/code&gt;. Let&apos;s see how they are used.&lt;/p&gt;
&lt;h2&gt;Fork away&lt;/h2&gt;
&lt;p&gt;When you fork a project, you make a copy of it under your own namespace. To work with the project you then clone the repository to your own machine. Let&apos;s use a repo I have forked as an example: &lt;a href=&quot;https://github.com/philnash/twilio-node&quot;&gt;Twilio&apos;s Node.js package&lt;/a&gt;. After forking, we clone the repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone git@github.com:philnash/twilio-node.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now see the first use case for &lt;code&gt;remote&lt;/code&gt;. Run &lt;code&gt;git remote&lt;/code&gt; in the &lt;code&gt;twilio-node&lt;/code&gt; directory and we see the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote
origin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, that&apos;s not too useful, it just shows we have one remote repo called &quot;origin&quot;. Running it again with the &lt;code&gt;--verbose&lt;/code&gt; flag shows a bit more information:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote --verbose
origin	git@github.com:philnash/twilio-node.git (fetch)
origin	git@github.com:philnash/twilio-node.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s better, this shows that the remote repository is the one we cloned and that we can fetch from and push to it.&lt;/p&gt;
&lt;h2&gt;Making changes&lt;/h2&gt;
&lt;p&gt;When contributing something back to the repo, it is easiest to do so on a branch. That keeps the master branch clean and makes it easy to keep up to date. Setting up your changes is covered by the six commands Cory listed in his tweet; you create a new branch with &lt;code&gt;branch&lt;/code&gt;, &lt;code&gt;checkout&lt;/code&gt; the branch, make the changes you want, &lt;code&gt;commit&lt;/code&gt; as many times as necessary and finally &lt;code&gt;push&lt;/code&gt; the branch to the origin, your fork. Then you can create your pull request and hope it gets accepted.&lt;/p&gt;
&lt;p&gt;Sometime later down the line you find you want to make another pull request, but the original repo has moved on. You need to update your fork so that you&apos;re working with the latest code. You could delete your fork and go through the process of forking and cloning the repo again, but that&apos;s a lot of unnecessary work. Instead, add the upstream repository as another remote for the repo and work with it from the command line.&lt;/p&gt;
&lt;h2&gt;Adding the upstream&lt;/h2&gt;
&lt;p&gt;To do this, we use the &lt;code&gt;remote&lt;/code&gt; command again, this time to &lt;code&gt;add&lt;/code&gt; a new remote. Find the original repo&apos;s URL and add it as a new remote. By convention this remote is called &quot;upstream&quot; though you can call it whatever you want.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote add upstream git@github.com:twilio/twilio-node.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we inspect the repo&apos;s remotes again, we will see both the origin and the upstream. You can use &lt;code&gt;-v&lt;/code&gt; as a shortcut for &lt;code&gt;--verbose&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git remote -v
origin	  git@github.com:philnash/twilio-node.git (fetch)
origin	  git@github.com:philnash/twilio-node.git (push)
upstream	git@github.com:twilio/twilio-node.git (fetch)
upstream	git@github.com:twilio/twilio-node.git (push)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Fetching the latest&lt;/h2&gt;
&lt;p&gt;To bring the repo up to date, we can now &lt;code&gt;fetch&lt;/code&gt; the latest from the upstream repo. It looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch upstream
remote: Counting objects: 6427, done.
remote: Compressing objects: 100% (549/549), done.
remote: Total 6427 (delta 5156), reused 5105 (delta 4939), pack-reused 934
Receiving objects: 100% (6427/6427), 2.54 MiB | 1.28 MiB/s, done.
Resolving deltas: 100% (5399/5399), completed with 391 local objects.
From github.com:twilio/twilio-node
   6a6733a8..73656c50  master           -&amp;gt; upstream/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git fetch&lt;/code&gt; downloads objects and refs from the repository, but doesn&apos;t apply it to the branch we are working on. We want to apply the updates to the master branch, so make sure it is checked out.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git checkout master
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Merging it all together&lt;/h2&gt;
&lt;p&gt;To bring the master branch up to date with the remote &lt;code&gt;merge&lt;/code&gt; the remote&apos;s master branch into our own, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git merge upstream/master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have kept your work off the master branch this will go ahead smoothly. Finally &lt;code&gt;push&lt;/code&gt; these updates to the fork so that it is up to date too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now everything on the master branch of the fork is up to date. If you need to update a different branch, substitute the branch name master for the branch you are working with.&lt;/p&gt;
&lt;h2&gt;Shortcuts&lt;/h2&gt;
&lt;p&gt;If you have been working with &lt;code&gt;git pull&lt;/code&gt; then you may have already seen a potential shortcut. &lt;code&gt;pull&lt;/code&gt; is the combination of &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;merge&lt;/code&gt;, so to perform these two actions in one command you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull upstream master
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Fork happy&lt;/h2&gt;
&lt;p&gt;That&apos;s all you need to know for keeping your fork up to date when contributing to open source. Add the upstream as a new &lt;code&gt;remote&lt;/code&gt; repo, &lt;code&gt;fetch&lt;/code&gt; the upstream repo and &lt;code&gt;merge&lt;/code&gt; the branch you want to update.&lt;/p&gt;
&lt;p&gt;For more detail on the various commands here, take a look at &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes&quot;&gt;&quot;Working with Remotes&quot; from the Pro Git book&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And remember, if you get stuck with something with git, check out &lt;a href=&quot;http://ohshitgit.com/&quot;&gt;Oh shit, git!&lt;/a&gt; for all your expletive based ways to save yourself.&lt;/p&gt;
&lt;p&gt;Happy forking!&lt;/p&gt;
</description><pubDate>Tue, 21 Aug 2018 00:00:00 GMT</pubDate><category>git</category><category>vcs</category><category>github</category></item><item><title>Spring clean your dev machine</title><link>https://philna.sh/blog/2018/05/27/spring-clean-your-dev-machine/</link><guid isPermaLink="true">https://philna.sh/blog/2018/05/27/spring-clean-your-dev-machine/</guid><description>&lt;p&gt;Development machines can build up such a lot of cruft. Old versions, oudated programs and unused caches litter the hard drive. It&apos;s good to take time once in a while to clean all of this up and free up some space.&lt;/p&gt;
&lt;p&gt;Here are some tips for commands you can run or actions you can take to clean up your machine. If you have a tip that I&apos;m missing here, please share it with me on &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Homebrew&lt;/h2&gt;
&lt;p&gt;If you&apos;re using &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; to manage packages on macOS you can run &lt;code&gt;brew cleanup&lt;/code&gt; to remove old versions of packages and old downloads from the cache.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew cleanup
Removing: ...
...
==&amp;gt; This operation has freed approximately 6.9GB of disk space.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the &lt;code&gt;-s&lt;/code&gt; flag scrubs the downloads for the latest package versions from the cache to give you back even more space. Thanks to &lt;a href=&quot;https://twitter.com/DavidGuyon&quot;&gt;David Guyon&lt;/a&gt; for that &lt;a href=&quot;https://twitter.com/DavidGuyon/status/995038050341281792&quot;&gt;tip&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To really turn it up to the max, &lt;a href=&quot;https://twitter.com/dalanmiller&quot;&gt;Daniel Miller&lt;/a&gt; &lt;a href=&quot;https://twitter.com/dalanmiller/status/994729616077082624&quot;&gt;suggested a bash alias&lt;/a&gt; to update Homebrew, upgrade packages and then cleanup after yourself. Add the following to your &lt;code&gt;.bash_profile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias bu=&quot;brew update &amp;amp;&amp;amp; brew upgrade &amp;amp;&amp;amp; brew cleanup&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the commands with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything should be up to date and leave no mess behind!&lt;/p&gt;
&lt;h3&gt;Homebrew bonus&lt;/h3&gt;
&lt;p&gt;Once all the caches have been tidied up, take a moment to make sure Homebrew  itself is running smoothly. Run the following command for a list of actions you can take to tidy up your install.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Docker&lt;/h2&gt;
&lt;p&gt;A few gigabytes of packages is a pretty good, but can we do better? If you&apos;re using &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; you can clean things up with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker volume prune
...
Total reclaimed space: 40.77GB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I am not a big Docker user myself, this was &lt;a href=&quot;https://twitter.com/JackWeirdy/status/991637143612215296&quot;&gt;a tip from Jack Wearden&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Rubies&lt;/h2&gt;
&lt;p&gt;I install multiple versions of Ruby using &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt; and &lt;a href=&quot;https://github.com/rbenv/ruby-build&quot;&gt;ruby-build&lt;/a&gt;. I just checked the versions I have installed and I found 12 Rubies that are beyond end of life. Since they also have their gems installed alongside them, clearing them out saved me tens to hundreds of megabytes per Ruby version.&lt;/p&gt;
&lt;p&gt;You can check the versions you have installed with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rbenv versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then uninstall an unwanted version of Ruby with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rbenv uninstall 2.1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nodes&lt;/h2&gt;
&lt;p&gt;I also use &lt;a href=&quot;https://github.com/creationix/nvm&quot;&gt;nvm&lt;/a&gt; to manage multiple versions of Node.js. The drill is the same here as with Ruby. Find the old Node versions that you have hanging around with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nvm ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then uninstall with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nvm uninstall v6.9.2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Any other ideas?&lt;/h2&gt;
&lt;p&gt;That should clear up a bunch of space on your machine, it sure did on mine. Now you have more room for more installs, more containers and more versions of more languages!&lt;/p&gt;
&lt;p&gt;I&apos;d love to collect any other tips that you might have to keep a development machine running smooth and lean. Let me know how you spring clean your development machine on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;footer&amp;gt;
&amp;lt;small&amp;gt;Dust icon by &amp;lt;a href=&quot;https://thenounproject.com/smalllike/collection/cleaning/?i=1683969&quot;&amp;gt;Smalllike from the Noun Project&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;
&amp;lt;/footer&amp;gt;&lt;/p&gt;
</description><pubDate>Sun, 27 May 2018 00:00:00 GMT</pubDate><category>maintenance</category><category>ruby</category><category>node</category><category>docker</category></item><item><title>Use the web share API easily with web components</title><link>https://philna.sh/blog/2018/04/25/web-share-api-with-web-components/</link><guid isPermaLink="true">https://philna.sh/blog/2018/04/25/web-share-api-with-web-components/</guid><description>&lt;p&gt;I&apos;m a fan of the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share&quot;&gt;web share API&lt;/a&gt; but I was not happy with &lt;a href=&quot;/blog/2017/03/14/the-web-share-api/&quot;&gt;my initial implementation of the API&lt;/a&gt;. It was all a bit complex for what is a very simple API. I wanted something more declarative and easier to use with a more generic fallback for when the share API was not available.&lt;/p&gt;
&lt;p&gt;I&apos;ve been playing about with &lt;a href=&quot;https://www.webcomponents.org/&quot;&gt;web components&lt;/a&gt; on and off over the last year and I wanted to see if I could make a component that would make using the web share API really easy. I think I&apos;ve done it and I hope you like it.&lt;/p&gt;
&lt;h2&gt;The &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image-left&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/web-share-wrapper/android-example.png&quot; alt=&quot;When you press the share button on Android, the share tray will appear.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;I&apos;ve built a web component called &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt;. It is a wrapper because it is intended to wrap around any existing elements that you might already be using as share buttons to various social networks and replace them with a single element that uses the web share API.&lt;/p&gt;
&lt;p&gt;If you are using Chrome on Android (the only current browser that supports the web share API) then you can check out the &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt; in action. Either scroll to the bottom of this article and click the share link or take a look at &lt;a href=&quot;https://philnash.github.io/web-share-wrapper/&quot;&gt;the examples in the repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Using the &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;There are instructions for using the &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt; in the &lt;a href=&quot;https://github.com/philnash/web-share-wrapper/blob/master/README.md&quot;&gt;readme on GitHub&lt;/a&gt; but here&apos;s a quick example.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;web-share-wrapper text=&quot;Share&quot; sharetext=&quot;Check out the web-share-wrapper web component&quot; shareurl=&quot;https://github.com/philnash/web-share-wrapper&quot;&amp;gt;
  &amp;lt;a href=&quot;https://twitter.com/intent/tweet/?text=Check%20out%20%40philnash&apos;s%20web-share-wrapper%20web%20component&amp;amp;amp;url=https%3A%2F%2Fgithub.com%2Fphilnash%2Fweb-share-wrapper&quot;&amp;gt;Share on Twitter&amp;lt;/a&amp;gt;
&amp;lt;/web-share-wrapper&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the web share API is not available, the above HTML will just be a link to share the project via a &lt;a href=&quot;https://dev.twitter.com/web/tweet-button/web-intent&quot;&gt;Twitter tweet intent&lt;/a&gt;. If the web share API is available, then the web component will kick in and replace the link with a button that, when clicked, will invoke the web share API. The button text is supplied by the &lt;code&gt;text&lt;/code&gt; attribute, the &lt;code&gt;sharetext&lt;/code&gt; attribute sets the body of the share and the &lt;code&gt;shareurl&lt;/code&gt; sets the link to share.&lt;/p&gt;
&lt;p&gt;The button is injected into the regular DOM, not the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM&quot;&gt;shadow DOM&lt;/a&gt;, so you can style it using regular CSS.&lt;/p&gt;
&lt;h3&gt;Getting fancy with templates&lt;/h3&gt;
&lt;p&gt;If a plain button isn&apos;t your style, the &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt; also supports using HTML templates. Instead of using the &lt;code&gt;text&lt;/code&gt; attribute to set the button text, use a &lt;code&gt;template&lt;/code&gt; attribute to point to the &lt;code&gt;id&lt;/code&gt; of a &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; on the page and the &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt; will hydrate that &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; and insert it in place of the content.&lt;/p&gt;
&lt;p&gt;For example, the following code will use an &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; instead of a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;template id=&quot;link&quot;&amp;gt;
  &amp;lt;a&amp;gt;Share link&amp;lt;/a&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;web-share-wrapper template=&quot;link&quot; sharetext=&quot;Check out the web-share-wrapper web component&quot; shareurl=&quot;https://github.com/philnash/web-share-wrapper&quot;&amp;gt;
  &amp;lt;a href=&quot;https://twitter.com/intent/tweet/?text=Check%20out%20%40philnash&apos;s%20web-share-wrapper%20web%20component&amp;amp;amp;url=https%3A%2F%2Fgithub.com%2Fphilnash%2Fweb-share-wrapper&quot;&amp;gt;Share on Twitter&amp;lt;/a&amp;gt;
&amp;lt;/web-share-wrapper&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, you can style the contents of the template anyway you like. The &lt;a href=&quot;https://philnash.github.io/web-share-wrapper/&quot;&gt;examples in the repo show a fancier button with an icon&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Installing  &lt;code&gt;&amp;lt;web-share-wrapper&amp;gt;&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The web component is published to Bower and you can install it to a project with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bower install --save web-share-wrapper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I am investigating other ways of publishing this web component as, while webcomponents.org seems to favour publishing to Bower, this doesn&apos;t seem like the best long term plan for a project. Support for npm seems like the most important next step.&lt;/p&gt;
&lt;h2&gt;What do you think?&lt;/h2&gt;
&lt;p&gt;You can now use the web share API declaratively, with just a single web component. I&apos;d love to know your thoughts on how this has been implemented and whether you think it&apos;s a good idea. Let me know on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt; or in the &lt;a href=&quot;https://github.com/philnash/web-share-wrapper/issues&quot;&gt;issues on the GitHub repo&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 25 Apr 2018 00:00:00 GMT</pubDate><category>javascript</category><category>web components</category><category>share</category></item><item><title>CSS: select first-of-class with the subsequent sibling combinator</title><link>https://philna.sh/blog/2018/03/18/css-first-of-class/</link><guid isPermaLink="true">https://philna.sh/blog/2018/03/18/css-first-of-class/</guid><description>&lt;p&gt;There are a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors&quot;&gt;whole bunch of CSS selectors&lt;/a&gt; available to web developers, but sometimes there&apos;s still not enough. I found this recently when building the &lt;a href=&quot;https://philna.sh/speaking/&quot;&gt;speaking section of my site&lt;/a&gt; and wanted to use the non-existent &lt;code&gt;:first-of-class&lt;/code&gt; pseudo class to apply some styles.&lt;/p&gt;
&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;The pseudo class &lt;code&gt;:first-of-type&lt;/code&gt; does exist, but it is limited. It is a special case of the &lt;code&gt;:nth-of-type()&lt;/code&gt; pseudo class. From the spec (emphasis mine):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The :nth-of-type(An+B) pseudo-class notation represents the same elements that would be matched by :nth-child(|An+B| of S), &lt;em&gt;&lt;strong&gt;where S is a type selector&lt;/strong&gt;&lt;/em&gt; and namespace prefix matching the element in question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That means, when using &lt;code&gt;:nth-of-type()&lt;/code&gt; or &lt;code&gt;:first-of-type&lt;/code&gt; the selector can only be a type selector. That is, you can only refer directly to elements—like &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;—when using &lt;code&gt;:first-of-type&lt;/code&gt;. So, if you had the following HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;p&amp;gt;This is paragraph 1&amp;lt;/p&amp;gt;
  &amp;lt;p class=&quot;special&quot;&amp;gt;This is paragraph 2&amp;lt;/p&amp;gt;
  &amp;lt;p class=&quot;special&quot;&amp;gt;This is paragraph 3&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this CSS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div p { color: #333; }
div p:first-of-type { color: red; }
div p.special:first-of-type { color: green; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might expect paragraph 1 to be coloured red and paragraph 2 to be coloured green. But &lt;code&gt;div p.special&lt;/code&gt; is not a type selector, so &lt;code&gt;:first-of-type&lt;/code&gt; doesn&apos;t apply here and nothing is green.&lt;/p&gt;
&lt;h2&gt;The subsequent sibling combinator&lt;/h2&gt;
&lt;p&gt;This can be solved with a technique created by &lt;a href=&quot;https://stackoverflow.com/questions/2717480/css-selector-for-first-element-with-class&quot;&gt;Daniel Tan&lt;/a&gt; and &lt;a href=&quot;https://stackoverflow.com/questions/5287272/css-select-first-element-with-a-certain-class/5293095#5293095&quot;&gt;Lea Verou&lt;/a&gt; and shared on Stack Overflow. Rather than using pseudo classes at all, we can use the &lt;a href=&quot;https://www.w3.org/TR/selectors/#general-sibling-combinators&quot;&gt;subsequent or general sibling combinator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The subsequent sibling combinator looks like &lt;code&gt;A ~ B&lt;/code&gt; where &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; are two compound selectors (not just type selectors). It allows you to select &lt;code&gt;B&lt;/code&gt; where &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; share a parent and &lt;code&gt;A&lt;/code&gt; precedes &lt;code&gt;B&lt;/code&gt; in the document.&lt;/p&gt;
&lt;p&gt;With the following HTML, the selector &lt;code&gt;h2 ~ p&lt;/code&gt; would select the second &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; as it follows the &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; but not the first &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;h1&amp;gt;Heading&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;Meta data&amp;lt;/p&amp;gt;
  &amp;lt;h2&amp;gt;Subheading&amp;lt;/h2&amp;gt;
  &amp;lt;p&amp;gt;Some article text&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The subsequent sibling combinator and :first-of-class&lt;/h2&gt;
&lt;p&gt;The trick to using the combinator to emulate a &lt;code&gt;:first-of-class&lt;/code&gt; psuedo class is to use a regular selector to style all the elements of the class with the style you want. Then use the combinator to turn it off for all but the first element. In our original example, the CSS now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div p { color: #333; }
div p:first-of-type { color: red; }
div p.special { color: green; }
div p.special ~ p.special { color: #333; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the first paragraph will be red, the second green and the last one grey. You can check out a &lt;a href=&quot;https://codepen.io/philnash/pen/WzoNwG/&quot;&gt;live example of this on Codepen&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;CSS Selectors level 4&lt;/h2&gt;
&lt;p&gt;After posting this, &lt;a href=&quot;https://twitter.com/simevidas&quot;&gt;Šime Vidas&lt;/a&gt; &lt;a href=&quot;https://twitter.com/simevidas/status/975394813863432192&quot;&gt;pointed out on Twitter&lt;/a&gt; that the future of CSS holds more promise for this style of selector. In the CSS Selectors level 4 specification the &lt;code&gt;:nth-child()&lt;/code&gt; pseudo class takes an argument that looks like: &lt;code&gt;An+B [of S]?&lt;/code&gt;. The &lt;code&gt;An+B&lt;/code&gt; part means you can provide a function to calculate what &lt;code&gt;n&lt;/code&gt; is, but the optional &lt;code&gt;of S&lt;/code&gt; means the pseudo class will match the Nth element that matches the selector.&lt;/p&gt;
&lt;p&gt;This means we can update our example to use &lt;code&gt;:nth-child()&lt;/code&gt; instead of two rules like we did above. Check out the CSS below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div p { color: #333; }
div p:first-of-type { color: red; }
div :nth-child(1 of p.special) { color: green; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;:nth-child(1 of p.special)&lt;/code&gt; means we are selecting the first child of the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; that is a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; with a class of &quot;special&quot;. This is exactly what I wanted.&lt;/p&gt;
&lt;p&gt;The only drawback with this technique? It only works in Safari right now. I&apos;ve updated the &lt;a href=&quot;https://codepen.io/philnash/pen/WzoNwG/&quot;&gt;Codepen, check it out in Safari to see all your future selector dreams come true&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;CSS hacking is still fun&lt;/h2&gt;
&lt;p&gt;I&apos;ve been writing CSS on and off for more than a decade now and while all the modern layout capabilities of CSS mean that there&apos;s a lot less hacking, sometimes you need to come up with a creative solution to a problem. I could have solved this with an extra class or a rearrangement of my HTML, but when CSS can do the job for you it feels more satisfying.&lt;/p&gt;
&lt;p&gt;Thanks to Daniel and Lea for sharing their solutions on Stack Overflow and particularly to Daniel who&apos;s &lt;a href=&quot;https://stackoverflow.com/questions/2717480/css-selector-for-first-element-with-class/8539107#8539107&quot;&gt;answer goes into a lot more detail about CSS selector and pseudo class misunderstandings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Maybe one day we&apos;ll see a &lt;code&gt;:first-of-class&lt;/code&gt; pseudo class. With the latest version of &lt;code&gt;:nth-child()&lt;/code&gt; maybe we don&apos;t even need a &lt;code&gt;:first-of-class&lt;/code&gt;, we&apos;re just waiting for browser support. In the meantime the subsequent sibling combinator remains our friend.&lt;/p&gt;
</description><pubDate>Sun, 18 Mar 2018 00:00:00 GMT</pubDate><category>CSS</category><category>CSS selectors</category></item><item><title>gzip a file in Ruby</title><link>https://philna.sh/blog/2018/02/25/gzip-file-ruby/</link><guid isPermaLink="true">https://philna.sh/blog/2018/02/25/gzip-file-ruby/</guid><description>&lt;p&gt;At the start of the year I looked into how to better compress the output of a &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; site. I&apos;ll write up the results to that soon. For now, here&apos;s how to gzip a file using Ruby.&lt;/p&gt;
&lt;h2&gt;Enter zlib&lt;/h2&gt;
&lt;p&gt;Contained within the Ruby standard library is the &lt;a href=&quot;http://ruby-doc.org/stdlib-2.5.0/libdoc/zlib/rdoc/Zlib.html&quot;&gt;&lt;code&gt;Zlib&lt;/code&gt; module&lt;/a&gt; which gives access to the underlying &lt;a href=&quot;https://zlib.net/&quot;&gt;zlib library&lt;/a&gt;. It is used to read and write files in gzip format. Here&apos;s a small program to read in a file, compress it and save it as a gzip file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;zlib&quot;

def compress_file(file_name)
  zipped = &quot;#{file_name}.gz&quot;

  Zlib::GzipWriter.open(zipped) do |gz|
    gz.write IO.binread(file_name)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use any IO or IO-like object with &lt;code&gt;Zlib::GzipWriter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can enhance this by adding the files original name into the compressed output. Also, it can be beneficial to set the file&apos;s modified time to the same as the original, particularly if you are using the file with &lt;a href=&quot;http://nginx.org/en/docs/http/ngx_http_gzip_static_module.html&quot;&gt;nginx&apos;s &lt;code&gt;gzip_static&lt;/code&gt; module&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;zlib&quot;

def compress_file(file_name)
  zipped = &quot;#{file_name}.gz&quot;

  Zlib::GzipWriter.open(zipped) do |gz|
    gz.mtime = File.mtime(file_name)
    gz.orig_name = file_name
    gz.write IO.binread(file_name)
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just to see how well this is working, we can print some statistics from the compression.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &quot;zlib&quot;

def print_stats(file_name, zipped)
  original_size = File.size(file_name)
  zipped_size = File.size(zipped)
  percentage_difference = ((original_size - zipped_size).to_f/original_size)*100
  puts &quot;#{file_name}: #{original_size}&quot;
  puts &quot;#{zipped}: #{zipped_size}&quot;
  puts &quot;difference - #{&apos;%.2f&apos; % percentage_difference}%&quot;
end

def compress_file(file_name)
  zipped = &quot;#{file_name}.gz&quot;

  Zlib::GzipWriter.open(zipped) do |gz|
    gz.mtime = File.mtime(file_name)
    gz.orig_name = file_name
    gz.write IO.binread(file_name)
  end

  print_stats(file_name, zipped)
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can pick a file and compress it with Ruby and gzip. For this quick test, I chose the uncompressed, unminified jQuery version 3.3.1. The results were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./jquery.js: 271751
./jquery.js.gz: 80669
difference - 70.32%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty good, but we can squeeze more out of it if we try a little harder.&lt;/p&gt;
&lt;h2&gt;Compression levels&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Zlib::GzipWriter&lt;/code&gt; takes a second argument to &lt;code&gt;open&lt;/code&gt; which is the level of compression zlib applies. 0 is no compression and 9 is the best possible compression and there&apos;s a trade off as you progress from 0 to 9 between speed and compression. The default compression is a good compromise between speed and compression, but if time isn&apos;t a worry for you then you might as well make the file as small as possible, especially if you want to serve it over the web. To set the compression level to 9 you can just use the integer, but &lt;code&gt;Zlib&lt;/code&gt; has a convenient constant for it: &lt;code&gt;Zlib::BEST_COMPRESSION&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Changing the line with &lt;code&gt;Zlib::GzipWriter&lt;/code&gt; in it to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  Zlib::GzipWriter.open(zipped, Zlib::BEST_COMPRESSION) do |gz|
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and running the file against jQuery again gives you:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./jquery.js: 271751
./jquery.js.gz: 80268
difference - 70.46%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A difference of 0.14 percentage points! Ok, not a huge win, but if the time to generate the file doesn&apos;t matter then you might as well. And the difference is greater on even larger files.&lt;/p&gt;
&lt;h2&gt;Streaming gzip&lt;/h2&gt;
&lt;p&gt;There&apos;s one last thing you might want to add. If you are compressing really large files, loading them entirely into memory isn&apos;t the most efficient way to work. Gzip is a streaming format though, so you can write chunks at a time to it. In this case, you just need to read the file you are compressing incrementally and write the chunks to the &lt;code&gt;GzipWriter&lt;/code&gt;. Here&apos;s what it would look like to read the file in chunks of 16kb:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def compress_file(file_name)
  zipped = &quot;#{file_name}.gz&quot;

  Zlib::GzipWriter.open(zipped, Zlib::BEST_COMPRESSION) do |gz|
    gz.mtime = File.mtime(file_name)
    gz.orig_name = file_name
    File.open(file_name) do |file|
      while chunk = file.read(16*1024) do
        gz.write(chunk)
      end
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;gzip in Ruby&lt;/h2&gt;
&lt;p&gt;So that is how to gzip a file using Ruby and zlib, as well as how to add extra information and control the compression level to balance speed against final filesize. All of this went into a gem I created recently to use maximum gzip compression on the output of a Jekyll site. The gem is called &lt;a href=&quot;https://github.com/philnash/jekyll-gzip&quot;&gt;jekyll-gzip&lt;/a&gt; and I&apos;ll be writing more about it, as well as other tools that are better than the zlib implementation of gzip, soon.&lt;/p&gt;
&lt;p&gt;&amp;lt;footer&amp;gt;
&amp;lt;small&amp;gt;Header icons: &amp;lt;a href=&quot;https://thenounproject.com/term/diamond/315/&quot;&amp;gt;Diamond by Edward Boatman&amp;lt;/a&amp;gt; and &amp;lt;a href=&quot;https://thenounproject.com/search/?q=vice&amp;amp;i=1554537&quot;&amp;gt;vice by Daniel Luft&amp;lt;/a&amp;gt; from the Noun Project.&amp;lt;/small&amp;gt;
&amp;lt;/footer&amp;gt;&lt;/p&gt;
</description><pubDate>Sun, 25 Feb 2018 00:00:00 GMT</pubDate><category>ruby</category><category>gzip</category><category>zlib</category><category>file compression</category></item><item><title>Permissions on the web suck</title><link>https://philna.sh/blog/2018/01/08/permissions-on-the-web-suck/</link><guid isPermaLink="true">https://philna.sh/blog/2018/01/08/permissions-on-the-web-suck/</guid><description>&lt;p&gt;I am a fan of progressive web apps and the powers that they bestow on web developers to build the next generation of applications. We can write web applications that work offline, &lt;a href=&quot;https://philna.sh/blog/2017/07/04/experimenting-with-the-background-fetch-api/&quot;&gt;download large files in the background&lt;/a&gt;, &lt;a href=&quot;https://www.twilio.com/blog/2016/02/web-powered-sms-inbox-with-service-worker-push-notifications.html&quot;&gt;send push notifications&lt;/a&gt;, and much more. I was so excited about push notifications on the web that I wrote a whole talk about it in 2015 and was fortunate enough to give it in a bunch of places around the world.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;post-video&quot;&amp;gt;
&amp;lt;iframe src=&quot;https://www.youtube.com/embed/4-WnlHhqcjU?t=14&quot; frameborder=&quot;0&quot; gesture=&quot;media&quot; allow=&quot;encrypted-media&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;But perhaps I was a little too prescient with that talk title, &quot;The web is getting pushy.&quot; Web applications themselves are getting pushy and now I see tweets like this:&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Dear Every Single Website in Existence,&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;No, I do not want Notifications turned on.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Thanks,&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Dan&amp;lt;/p&amp;gt;— Dan Szymborski (@DSzymborski) &amp;lt;a href=&quot;https://twitter.com/DSzymborski/status/921260402146672641?ref_src=twsrc%5Etfw&quot;&amp;gt;October 20, 2017&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;And this:&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Dear websites: no one wants push notifications. Love, every person who uses the internet&amp;lt;/p&amp;gt;— Andrea Whitmer (@nutsandbolts) &amp;lt;a href=&quot;https://twitter.com/nutsandbolts/status/925421346259193862?ref_src=twsrc%5Etfw&quot;&amp;gt;October 31, 2017&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;And blog posts like How-To Geek&apos;s &lt;a href=&quot;https://www.howtogeek.com/288946/how-to-stop-websites-from-asking-to-show-notifications/&quot;&gt;how to stop websites from asking to show notifications&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Push notifications are getting a bad reputation and I don&apos;t think they deserve it. Here are the problems I think we are facing with this and some potential solutions.&lt;/p&gt;
&lt;h2&gt;It&apos;s not the fault of the notifications&lt;/h2&gt;
&lt;p&gt;I have a theory. It&apos;s not that users don&apos;t want push notifications. There is a time and a place for a good push notification. Native mobile application developers seem to be getting this right now, at least in my experience, and &lt;a href=&quot;https://medium.com/the-guardian-mobile-innovation-lab/generating-images-in-javascript-without-using-the-canvas-api-77f3f4355fad&quot;&gt;innovative web teams like those at the Guardian have done some really interesting and impressive push notification experiments&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image-left&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/permissions/guardian.png&quot; alt=&quot;The Guardian&apos;s experiment with creating images for the current state of the UK General Election entirely within a push notification.&quot; class=&quot;post-image-left&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;My theory is that users might want push notifications. They might want them for newsworthy moments, like the Guardian&apos;s election night experiments. They might want notifications that someone has sent them a message or that their taxi is arriving or their flight has been delayed. There are countless reasons a user might want to receive push notifications.&lt;/p&gt;
&lt;p&gt;But the top way to annoy any user is to pop up that permission dialog asking to send push notifications on page load without any context, any information at all, that would allow them to make that decision.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/permissions/sitepoint.png&quot; alt=&quot;Sitepoint pops up a permissions dialog on page load&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;I noticed &lt;a href=&quot;http://sitepoint.com/&quot;&gt;Sitepoint&lt;/a&gt;, a web tutorial site that really should know better, doing this. And other well known sites; &lt;a href=&quot;https://www.producthunt.com/&quot;&gt;Product Hunt&lt;/a&gt;, &lt;a href=&quot;https://www.cnet.com/&quot;&gt;cnet&lt;/a&gt; and even &lt;a href=&quot;https://www.facebook.com/&quot;&gt;Facebook&lt;/a&gt; in their early experimentation with the feature, have been spotted doing it too. There are probably many more examples.&lt;/p&gt;
&lt;p&gt;These permission dialogs suck.&lt;/p&gt;
&lt;h2&gt;Permission for what?&lt;/h2&gt;
&lt;p&gt;Read that dialog from the screenshot again. All it says is &quot;www.sitepoint.com wants to show notifications&quot; and there are two buttons, &quot;Block&quot; or &quot;Allow&quot;. It doesn&apos;t say what the notifications will contain, how often they might be sent, why the user should even care. That permission dialog can&apos;t say that. There is nothing in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/PushManager&quot;&gt;&lt;code&gt;PushManager&lt;/code&gt; API&lt;/a&gt; that can be used to add any context to a popup like this.&lt;/p&gt;
&lt;p&gt;I believe that the intention for the API is to encourage developers to build an intermediary step where the application explains why it wants permission to send push notifications. Then when the user agrees to that, trigger the real permission notification.&lt;/p&gt;
&lt;p&gt;This is a pattern that Matt Gaunt explains beautifully with his airline example from &lt;a href=&quot;https://developers.google.com/web/fundamentals/push-notifications/permission-ux&quot;&gt;his article on permissions UX&lt;/a&gt;. The real key to each of the patterns in Matt&apos;s article is that the permission dialog never surprises the user, they always know why they are being asked permission by the browser to send notifications.&lt;/p&gt;
&lt;h2&gt;Permissions solved&lt;/h2&gt;
&lt;p&gt;If everyone just reads Matt&apos;s article and implements friendly patterns for asking for permission then everything is solved, right? If only.&lt;/p&gt;
&lt;p&gt;It doesn&apos;t matter how good an article on UX is or how many people read it, it can&apos;t reach everyone. So we still end up with permissions popping up at page load. You might think that this is just bad for the site that is providing the poor experience, but check those tweets at the start of this post. They don&apos;t care any more, they never want notifications. They want to be able to turn them all off for good. And they can, that&apos;s what the How-To Geek article explains. Firefox is soon releasing a global disable option too, and if you read the responses you&apos;ll see that this is being welcomed.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;There is now a global &quot;disable&quot; option for all push notification prompts&amp;lt;a href=&quot;https://t.co/b01ZCoWOfK&quot;&amp;gt;https://t.co/b01ZCoWOfK&amp;lt;/a&amp;gt; &amp;lt;a href=&quot;https://t.co/0JSSYoaysb&quot;&amp;gt;pic.twitter.com/0JSSYoaysb&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;— Firefox Nightly (@FirefoxNightly) &amp;lt;a href=&quot;https://twitter.com/FirefoxNightly/status/950321939129815040?ref_src=twsrc%5Etfw&quot;&amp;gt;January 8, 2018&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;The problem here is that all these poor experiences with permissions are causing users to revoke the permission not just from the offending sites but from the entire platform. Once notifications are turned off globally, it&apos;s almost impossible to get users to turn them back on again. Now, even if you&apos;ve built the best push notification permission flow, a user who has outright blocked notifications will never see it and never experience your application the way you built it.&lt;/p&gt;
&lt;p&gt;I think we need more than just best practice UX articles to solve this.&lt;/p&gt;
&lt;h2&gt;Power to the browser&lt;/h2&gt;
&lt;p&gt;I believe the power is in the hands of the browsers. We&apos;ve seen them deal with unnecessary popups before. Remember, back in the day, installing popup blockers because the web was a fraught mess of windows popping up everywhere. Now every browser has a built in popup blocker. Most of what these built in popup blockers do is restrict the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/open&quot;&gt;&lt;code&gt;window.open&lt;/code&gt; function&lt;/a&gt; to only work on user interaction. This means that users browsing the web don&apos;t end up with a whole bunch of unexpected popups or worse, popunders.&lt;/p&gt;
&lt;p&gt;I would like to see the same for permission dialogs. If the browsers enforced a user interaction before you could show a permission dialog then the page load permission dialog would disappear immediately. The platform would then encourage all developers to explain the permission before asking for it and lead to better experiences all round.&lt;/p&gt;
&lt;h2&gt;Together we can save permissions&lt;/h2&gt;
&lt;p&gt;It&apos;s going to be a team effort, but I think we can save permissions dialogs, push notifications and the web platform.&lt;/p&gt;
&lt;p&gt;&amp;lt;strong&amp;gt;Developers&amp;lt;/strong&amp;gt;: never show a permissions dialog on page load, instead read &lt;a href=&quot;https://developers.google.com/web/fundamentals/push-notifications/permission-ux&quot;&gt;Matt Gaunt&apos;s article on permission UX&lt;/a&gt; and build better experiences.&lt;/p&gt;
&lt;p&gt;&amp;lt;strong&amp;gt;Browsers&amp;lt;/strong&amp;gt;: please consider a permissions popup blocker and encourage developers to build better applications by making poor experiences harder to build.&lt;/p&gt;
&lt;p&gt;&amp;lt;strong&amp;gt;Users&amp;lt;/strong&amp;gt;: don&apos;t block all permissions, please, you might be missing out on something really useful somewhere else on the web.&lt;/p&gt;
&lt;p&gt;I hope we can all agree that there are uses for push notifications, what we really need to fix are the permissions.&lt;/p&gt;
</description><pubDate>Mon, 08 Jan 2018 00:00:00 GMT</pubDate><category>ux</category><category>pwa</category><category>push notifications</category><category>permission</category></item><item><title>Two tests you should run against your Ruby project now</title><link>https://philna.sh/blog/2017/07/12/two-tests-you-should-run-against-your-ruby-project-now/</link><guid isPermaLink="true">https://philna.sh/blog/2017/07/12/two-tests-you-should-run-against-your-ruby-project-now/</guid><description>&lt;p&gt;I had the fortune of attending the wonderful &lt;a href=&quot;https://brightonruby.com/&quot;&gt;Brighton Ruby conference&lt;/a&gt; last week. It was full of excellent advice, wisdom and code. There was one talk, however, that urged me to go home and do something straight away to ensure the security and usability of my projects.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/teabass&quot;&gt;Andrew Nesbitt&lt;/a&gt;, with the backing of the &lt;a href=&quot;https://libraries.io/data&quot;&gt;libraries.io open data&lt;/a&gt;, told tales of the hidden costs of open source software in Ruby and beyond. &lt;a href=&quot;https://speakerdeck.com/andrew/can-my-friends-come-too&quot;&gt;His slides are available here&lt;/a&gt; and I will link to the video when it is published.&lt;/p&gt;
&lt;h2&gt;Counting the costs&lt;/h2&gt;
&lt;p&gt;There were two take aways from Andrew&apos;s talk that could be acted on immediately: check up on security vulnerabilities and license issues in my projects.&lt;/p&gt;
&lt;h2&gt;Security audits&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://rubysec.com/&quot;&gt;Rubysec&lt;/a&gt; maintains the &lt;a href=&quot;https://github.com/rubysec/ruby-advisory-db&quot;&gt;Ruby Advisory Database&lt;/a&gt;, a database of vulnerable Ruby gems. The database currently contains 287 advisories across 147 gems. If you&apos;re worried that your project depends on a vulnerable gem they publish &lt;a href=&quot;https://github.com/rubysec/bundler-audit&quot;&gt;bundler-audit&lt;/a&gt;, which is a gem that audits &lt;code&gt;Gemfile.lock&lt;/code&gt;s against the their database.&lt;/p&gt;
&lt;p&gt;To use bundler-audit you need to install it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gem install bundler-audit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and run it in your project directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ bundle-audit
No vulnerabilities found
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Second opinions&lt;/h3&gt;
&lt;p&gt;If one security audit isn&apos;t enough to quell your fears then you are in luck. While it isn&apos;t a Ruby tool and Andrew didn&apos;t mention it, &lt;a href=&quot;https://snyk.io/&quot;&gt;Snyk&lt;/a&gt; is a tool for monitoring vulnerabilities in Ruby, JavaScript and Java projects via their own &lt;a href=&quot;https://snyk.io/vuln/&quot;&gt;vulnerability database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They publish a &lt;a href=&quot;https://snyk.io/docs/using-snyk&quot;&gt;command line interface&lt;/a&gt; that you can use to check your applications. You do need to &lt;a href=&quot;https://snyk.io/signup&quot;&gt;create an account&lt;/a&gt; to use it and install Node.js and npm to run it. Once over those hurdles you can install and run it against your project like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# install
$ npm install -g snyk
# authenticate the client
$ snyk auth
# test the project in the current directory
$ snyk test
✓ Tested 23 dependencies for known vulnerabilities, no vulnerable paths found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now security has been checked, how about licenses?&lt;/p&gt;
&lt;h2&gt;Licensing audits&lt;/h2&gt;
&lt;p&gt;Unlicensed code is copyrighted code that you do not have the permission to use.&lt;/p&gt;
&lt;p&gt;This may not sound bad until you hear that &lt;em&gt;28% of all Ruby gems have no license declared&lt;/em&gt;. That is over 37,000 gems. And you don&apos;t need to worry about just the gems that you install, but their dependencies. And their dependencies. And their... it goes on.&lt;/p&gt;
&lt;p&gt;Thankfully there is a tool to check this too. &lt;a href=&quot;https://github.com/pivotal/LicenseFinder/&quot;&gt;&lt;code&gt;license_finder&lt;/code&gt; is maintained by Pivotal&lt;/a&gt; and checks your dependencies for licenses. The tool reports back with each dependency&apos;s license. You then have to decide whether the license fits with your project. You can choose to accept each of your dependencies and you can bulk accept by whitelisting certain licenses. If you&apos;re not using Ruby, &lt;a href=&quot;https://github.com/pivotal/LicenseFinder#supported-project-types&quot;&gt;&lt;code&gt;license_finder&lt;/code&gt; works with many types of project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You install &lt;code&gt;license_finder&lt;/code&gt; with Ruby gems.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gem install license_finder
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you run it within a project directory. This was the result when I ran it against &lt;a href=&quot;https://github.com/philnash/envyable&quot;&gt;&lt;code&gt;envyable&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ license_finder
..............................................................................
Dependencies that need approval:
bundler, 1.12.5, MIT
codeclimate-test-reporter, 1.0.8, MIT
docile, 1.1.5, MIT
envyable, 1.2.0, MIT
minitest, 5.10.2, MIT
rake, 12.0.0, MIT
simplecov, 0.13.0, MIT
simplecov-html, 0.10.1, MIT
thor, 0.19.4, MIT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when I whitelist the MIT license and run it again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ license_finder whitelist add MIT
$ license_finder
..............................................................................
All dependencies are approved for use
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The licenses are approved.&lt;/p&gt;
&lt;h2&gt;Test all the time&lt;/h2&gt;
&lt;p&gt;Whether there is a security vulnerability or an unlicensed project you could be subjecting yourself or your users to problems.&lt;/p&gt;
&lt;p&gt;It doesn&apos;t take long to install and run these tools and I encourage you to do so too, they may save you from your dependencies. It takes a little longer, but it&apos;s probably worth it, to add them to your CI setup too.&lt;/p&gt;
&lt;p&gt;To find out more look out for the video of Andrew&apos;s talk, check out &lt;a href=&quot;https://libraries.io/&quot;&gt;libraries.io&lt;/a&gt;&apos;s list of &lt;a href=&quot;https://libraries.io/unlicensed-libraries&quot;&gt;libraries without licenses&lt;/a&gt; or even &lt;a href=&quot;https://libraries.io/data&quot;&gt;take a dive into the data&lt;/a&gt; and see what you can find out about our open source ecosystem.&lt;/p&gt;
</description><pubDate>Wed, 12 Jul 2017 00:00:00 GMT</pubDate><category>ruby</category><category>security</category><category>licenses</category><category>copyright</category></item><item><title>Experimenting with the background fetch API</title><link>https://philna.sh/blog/2017/07/04/experimenting-with-the-background-fetch-api/</link><guid isPermaLink="true">https://philna.sh/blog/2017/07/04/experimenting-with-the-background-fetch-api/</guid><description>&lt;p&gt;The service worker API is expanding as more ways to use the background dwelling worker emerge. I&apos;ve written before about &lt;a href=&quot;https://www.twilio.com/blog/2016/02/web-powered-sms-inbox-with-service-worker-push-notifications.html&quot;&gt;push notifications&lt;/a&gt; and &lt;a href=&quot;https://www.twilio.com/blog/2017/02/send-messages-when-youre-back-online-with-service-workers-and-background-sync.html&quot;&gt;background sync&lt;/a&gt; and I recently explored the very new &lt;a href=&quot;https://github.com/WICG/background-fetch&quot;&gt;background fetch API&lt;/a&gt;. Here&apos;s what I found out about it.&lt;/p&gt;
&lt;h2&gt;Downloads and uploads&lt;/h2&gt;
&lt;p&gt;The background fetch API seeks to solve two problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When a service worker is downloading large files to cache and the user navigates away, even when using &lt;code&gt;event.waitUntil&lt;/code&gt; the service worker may eventually be killed&lt;/li&gt;
&lt;li&gt;When uploading files and the user navigates away the upload is interrupted and will fail&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The background fetch API allows a developer to perform and control fetches to large files or lists of files outside the context of a single page. This can increase the likelihood of successful uploads and downloads and allow the service worker to cache the results too.&lt;/p&gt;
&lt;p&gt;This API is available to test, at the time of writing, in Chrome with the experimental web platform features flag turned on in the chrome://flags settings.&lt;/p&gt;
&lt;h2&gt;Testing out an example&lt;/h2&gt;
&lt;p&gt;To try to understand how the API worked, I decided to build a simple proof of concept. The &lt;a href=&quot;https://fan-hubcap.glitch.me/&quot;&gt;example can be found here on Glitch&lt;/a&gt; or you can &lt;a href=&quot;https://github.com/philnash/service-worker-background-fetch&quot;&gt;check out and run the source code from GitHub&lt;/a&gt;. I didn&apos;t want to mess around with large files, so this example tries to load an image that has a 10 second delay on it (4.5 seconds on Glitch).&lt;/p&gt;
&lt;p&gt;There is a service worker set up to serve the image directly from the cache if it is present, otherwise it goes to the network.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// sw.js
self.addEventListener(&apos;fetch&apos;, function(event) {
  if (event.request.url.match(/images/)) {
    event.respondWith(
      caches.open(&apos;downloads&apos;).then(cache =&amp;gt; {
        return cache.match(event.request).then(response =&amp;gt; {
          return response || fetch(event.request);
        });
      })
    );
  } else {
    event.respondWith(caches.match(event.request));
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty standard service worker stuff so far, here&apos;s the new stuff.&lt;/p&gt;
&lt;p&gt;Clicking on the &quot;Start download&quot; button will kick off a background fetch. The fetch is tagged &apos;large-file&apos; (I was feeling creative). While the background fetch is going on, you can navigate away from the page, the download will continue to work in the background.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// index.html
if (&apos;serviceWorker&apos; in navigator) {
  navigator.serviceWorker.register(&apos;/sw.js&apos;).then(function(reg) {
    const button = document.getElementById(&apos;download&apos;);
    if (&apos;backgroundFetch&apos; in reg) {
      button.addEventListener(&apos;click&apos;, function(event) {
        reg.backgroundFetch.fetch(&apos;large-file&apos;, [new Request(&apos;/images/twilio.png&apos;)], {
          title: &apos;Downloading large image&apos;
        }).then(function(backgroundFetch) { console.log(backgroundFetch) });
      })
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the download is complete the service worker receives the &lt;code&gt;backgroundfetched&lt;/code&gt; event. The event has a &lt;code&gt;fetches&lt;/code&gt; property that points to an array of objects containing each request and response. The code loops through the &lt;code&gt;fetches&lt;/code&gt; (even though in this case we know there&apos;s only one) and puts the response into the cache.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;self.addEventListener(&apos;backgroundfetched&apos;, function(event) {
  event.waitUntil(
    caches.open(&apos;downloads&apos;).then(function(cache) {
      event.updateUI(&apos;Large file downloaded&apos;);
      registration.showNotification(&apos;File downloaded!&apos;);
      const promises = event.fetches.map(({ request, response }) =&amp;gt; {
        if (response &amp;amp;&amp;amp; response.ok) {
          return cache.put(request, response.clone());
        }
      });
      return Promise.all(promises);
    })
  );
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, when the image is requested it will be served directly from the service worker cache, no more slow download.&lt;/p&gt;
&lt;h2&gt;Thoughts on the background fetch API&lt;/h2&gt;
&lt;p&gt;As you can see, it&apos;s not too hard to implement the background fetch API. There are some events I didn&apos;t implement on this initial exploration, the &lt;code&gt;backgroundfetchfail&lt;/code&gt;, &lt;code&gt;backgroundfetchabort&lt;/code&gt; and &lt;code&gt;backgroundfetchclick&lt;/code&gt; events. In a fully featured application you would expect to do so.&lt;/p&gt;
&lt;h3&gt;Audio difficulties&lt;/h3&gt;
&lt;p&gt;I initially started this experiment with an audio file, but found it hard to prove I had stored the audio file successfully in the cache. I don&apos;t blame the background fetch API for this, rather the &lt;a href=&quot;https://samdutton.github.io/samples/service-worker/prefetch-video/&quot;&gt;issues in dealing with range requests in the service worker fetch event&lt;/a&gt;. Given that this API is very useful for downloading large audio and video assets for rich applications, making this easier for developers should be an important part of service worker discussions for anyone working on this feature.&lt;/p&gt;
&lt;h3&gt;Popping up in the downloads&lt;/h3&gt;
&lt;p&gt;When testing this in Chrome on the desktop I was surprised to find that the file was downloaded in a user visible manner. My expectations were that the file would be left for the service worker to handle and cache. Instead it appeared in my downloads folder. If you were using the background fetch API to fetch resources for a game, for example, I would not expect to litter the user&apos;s downloads folder with resources that are only of use within the application. This didn&apos;t happen when I tested on an Android device though.&lt;/p&gt;
&lt;h3&gt;What UI?&lt;/h3&gt;
&lt;p&gt;Within the API there are places to affect the browser UI, you can set a title and icons for the fetch and update the title using the &lt;code&gt;event.updateUI&lt;/code&gt; method. However, I couldn&apos;t see this UI anywhere in my testing on desktop or mobile. I can only assume that this is being worked on and will arrive with another version of Canary.&lt;/p&gt;
&lt;h2&gt;To the future&lt;/h2&gt;
&lt;p&gt;I like to play with these early APIs, there&apos;s such a lot of potential in them (I&apos;m excited for the return of the &lt;a href=&quot;https://philna.sh/blog/2017/03/14/the-web-share-api/&quot;&gt;Web Share API&lt;/a&gt; too!). The background fetch API is going to be great for applications that require downloading large files or large amounts of files, with more control than just using the service worker cache. It&apos;s obviously going to be useful for video and audio applications, but games and virtual reality experiences will surely benefit from it too.&lt;/p&gt;
&lt;p&gt;I think there is still work to be done in browser to make this experience a good one for both developers and users, but it&apos;s exciting to see how the service worker is progressing. My next plan is to build an application that fully takes advantage of the background fetch API, as well as other service worker features, in a real world situation. Keep an eye on &lt;a href=&quot;http://github.com/philnash/&quot;&gt;my GitHub profile&lt;/a&gt; to follow the progress.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in the background fetch API there are &lt;a href=&quot;https://github.com/WICG/background-fetch&quot;&gt;more examples and discussion on the proposal on GitHub&lt;/a&gt;. Give me a shout on &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;Twitter&lt;/a&gt; if you&apos;re also excited about features like this coming to the browser.&lt;/p&gt;
</description><pubDate>Tue, 04 Jul 2017 00:00:00 GMT</pubDate><category>javascript</category><category>service worker</category></item><item><title>Speed up bundle install with this one trick</title><link>https://philna.sh/blog/2017/06/12/speed-up-bundle-install-with-this-one-trick/</link><guid isPermaLink="true">https://philna.sh/blog/2017/06/12/speed-up-bundle-install-with-this-one-trick/</guid><description>&lt;p&gt;Did you know &lt;a href=&quot;https://bundler.io&quot;&gt;bundler&lt;/a&gt; can download and install gems in parallel?&lt;/p&gt;
&lt;p&gt;That&apos;s right, using the &lt;code&gt;--jobs&lt;/code&gt; option for &lt;code&gt;bundle install&lt;/code&gt; means you can set the number of gems that can download and install at the same time. To use four jobs, for example, you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle install --jobs=4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a bonus, you don&apos;t even have to remember to use the &lt;code&gt;--jobs&lt;/code&gt; option every time. You can set this in your global bundler config with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle config --global jobs 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Does it really help?&lt;/h2&gt;
&lt;p&gt;I found all of this out recently, even though this has been available since bundler version 1.4.0, and I wanted to test how useful it actually was. Downloading and installing gems in parallel sounds like a delight, but I needed to test it with something to be sure.&lt;/p&gt;
&lt;p&gt;I went looking for a large Rails project to try this out on. I happened upon the &lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab-ce/&quot;&gt;GitLab Community Edition app&lt;/a&gt; and cloned the project. I didn&apos;t need to actually get the project running, I was just testing the installation time of the gems.&lt;/p&gt;
&lt;p&gt;I was looking for a large application. &lt;code&gt;rake stats&lt;/code&gt; told me that it contains 172 controllers, 208 models and 86,019 lines of code. In the Gemfile there are 197 gem dependencies which shakes out to 369 gems in total. I checked out &lt;a href=&quot;https://github.com/discourse/discourse&quot;&gt;Discourse&lt;/a&gt; and &lt;a href=&quot;https://github.com/diaspora/diaspora&quot;&gt;Diaspora&lt;/a&gt; too, but GitLab certainly had the largest number of gems, so it was perfect to test my theory.&lt;/p&gt;
&lt;p&gt;I ran &lt;code&gt;time bundle install --path=./gems --quiet --force --jobs=n&lt;/code&gt; five times each for n equal to 1 and 4. The median results for each were:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;time bundle install --path=./gems --quiet --force --jobs=1
real  4m39.836s
user  1m59.692s
sys 0m50.291s
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;time bundle install --path=./gems --quiet --force --jobs=4
real  2m55.857s
user  2m0.005s
sys 0m47.897s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These tests were run on a MacBook Pro with 2.5 GHz Intel Core i7 and 16GB RAM.&lt;/p&gt;
&lt;p&gt;With these results we can see that installing the gems for GitLab in parallel using 4 workers was ~1.6 times faster.&lt;/p&gt;
&lt;p&gt;You should run your own tests with your own setup to see if this genuinely will save you time with installing gems. There has been &lt;a href=&quot;http://archlever.blogspot.co.uk/2013/09/lies-damned-lies-and-truths-backed-by.html&quot;&gt;some research&lt;/a&gt; into the &lt;a href=&quot;http://blog.mroth.info/blog/2014/10/02/rubygems-bundler-they-took-our-jobs/&quot;&gt;best number of jobs&lt;/a&gt;. I&apos;ve certainly set the default number of jobs to 4 for the future.&lt;/p&gt;
&lt;h3&gt;Why isn&apos;t this default?&lt;/h3&gt;
&lt;p&gt;You might be wondering why Bundler doesn&apos;t just turn on parallel installation by default. A quick glance at the comments in the &lt;a href=&quot;https://github.com/bundler/bundler/blob/7f1411cdb3279c25e8e8f2a8e3c1f8acf3dbe8f2/lib/bundler/installer.rb#L160-L163&quot;&gt;source code&lt;/a&gt; gives this one away.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# the order that the resolver provides is significant, since
# dependencies might affect the installation of a gem.
# that said, it&apos;s a rare situation (other than rake), and parallel
# installation is SO MUCH FASTER. so we let people opt in.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Reading the docs&lt;/h2&gt;
&lt;p&gt;As a final point, I&apos;d like to point out that reading the documentation, even for a project you may use every day, can turn up some interesting options you didn&apos;t know you could use. Bundler itself is full of other useful commands and options, check out &lt;a href=&quot;https://bundler.io/v1.15/man/bundle-pristine.1.html&quot;&gt;&lt;code&gt;bundle pristine&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;https://bundler.io/v1.15/man/bundle-gem.1.html&quot;&gt;&lt;code&gt;bundle gem&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://bundler.io/v1.15/man/bundle-outdated.1.html&quot;&gt;&lt;code&gt;bundle outdated&lt;/code&gt;&lt;/a&gt; for some ideas of what is available.&lt;/p&gt;
&lt;p&gt;So, keep an eye out for interesting options in the software you use and go out there and install your gems faster.&lt;/p&gt;
</description><pubDate>Mon, 12 Jun 2017 00:00:00 GMT</pubDate><category>ruby</category><category>bundler</category></item><item><title>Always install Bundler alongside Ruby with rbenv</title><link>https://philna.sh/blog/2017/03/22/always-install-bundler-alongside-ruby-with-rbenv/</link><guid isPermaLink="true">https://philna.sh/blog/2017/03/22/always-install-bundler-alongside-ruby-with-rbenv/</guid><description>&lt;p&gt;Here&apos;s a quick tip for anyone who uses &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt; and &lt;a href=&quot;https://github.com/rbenv/ruby-build&quot;&gt;ruby-build&lt;/a&gt; to manage installing and switching Ruby versions.&lt;/p&gt;
&lt;p&gt;When installing a new version of Ruby I cannot think of a situation in which I wouldn&apos;t want &lt;a href=&quot;http://bundler.io/&quot;&gt;Bundler&lt;/a&gt; installed too. The good news is that you can get rbenv to install Bundler, or any other gem you like, as soon as you install a new version of Ruby. All you need is the &lt;a href=&quot;https://github.com/rbenv/rbenv-default-gems&quot;&gt;default-gems&lt;/a&gt; plugin.&lt;/p&gt;
&lt;h2&gt;Default gems&lt;/h2&gt;
&lt;p&gt;If you installed rbenv with &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;, like me, then you can install default-gems with Homebrew too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ brew install rbenv-default-gems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you need to make a list of gems that you want installed. First create a file named &lt;code&gt;default-gems&lt;/code&gt; in your &lt;code&gt;$(rbenv root)&lt;/code&gt; directory. Mine is located at &lt;code&gt;~/.rbenv/default-gems&lt;/code&gt;. Add the names of the gems you want to install, one per line. You can include an optional version or the option &lt;code&gt;--pre&lt;/code&gt; for a prerelease version. For Bundler you will want the latest release, so the &lt;code&gt;default-gems&lt;/code&gt; file would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundler
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when you install a new version of Ruby, Bundler will be installed as well.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/install_ruby_bundler.png&quot; alt=&quot;A terminal window shows the result of running &lt;code&gt;rbenv install 2.4.0&lt;/code&gt;. Not only is Ruby 2.4.0 installed, but so is the latest version of Bundler.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;You can add other gems that you always use too. If you&apos;re a fan of &lt;a href=&quot;http://pryrepl.org/&quot;&gt;pry&lt;/a&gt;, for example, then add a line for it in &lt;code&gt;default-gems&lt;/code&gt; and you&apos;ll never have to remember to install it yourself.&lt;/p&gt;
&lt;h2&gt;rbenv plugins&lt;/h2&gt;
&lt;p&gt;There are a number of &lt;a href=&quot;https://github.com/rbenv/rbenv/wiki/Plugins&quot;&gt;rbenv plugins&lt;/a&gt; which you can check out, but default-gems has always been a must have for me.&lt;/p&gt;
&lt;p&gt;Are there any tools or plugins you use that make your Ruby development easier? I&apos;d love to hear about them! Please &lt;a href=&quot;https://twitter.com&quot;&gt;share your tips with me on Twitter&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Wed, 22 Mar 2017 00:00:00 GMT</pubDate><category>ruby</category><category>bundler</category><category>rbenv</category></item><item><title>The web share API</title><link>https://philna.sh/blog/2017/03/14/the-web-share-api/</link><guid isPermaLink="true">https://philna.sh/blog/2017/03/14/the-web-share-api/</guid><description>&lt;p&gt;Recently I implemented the &lt;a href=&quot;https://developers.google.com/web/updates/2016/10/navigator-share&quot;&gt;web share API&lt;/a&gt; for my site as a means of testing it out. If you are using version 55 or above of Chrome on Android then you can see it in action by clicking &quot;share it&quot; at the bottom of this post.&lt;/p&gt;
&lt;p&gt;If you don&apos;t have a browser that supports the API, then this is what it looks like when you hit the share link.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;The Web Share API origin trial has finished and after consideration is now supported in Chrome for Android. You can keep up with support by checking out the &amp;lt;a href=&quot;https://caniuse.com/#search=web-share&quot;&amp;gt;Web Share API on Can I Use&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;I want to share how the API works and my thoughts on it so far.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-left&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/webshare.png&quot; alt=&quot;A tray slides up from the bottom of the screen with sharing options from your installed applications.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;The web share API is actually quite a simple API. It is available on the &lt;code&gt;navigator&lt;/code&gt; object and consists of one method; &lt;code&gt;share&lt;/code&gt;. The method takes an object that can have &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;url&lt;/code&gt; properties. It is mandatory to have either a &lt;code&gt;text&lt;/code&gt; or &lt;code&gt;url&lt;/code&gt; property, though you can have both.&lt;/p&gt;
&lt;p&gt;You can only invoke &lt;code&gt;navigator.share&lt;/code&gt; as a result of a user gesture, so sites can&apos;t surprise a user into sharing a page. Also, like other powerful new web platform features, it only works on sites hosted on HTTPS.&lt;/p&gt;
&lt;p&gt;The API is Promise based, when a page is successfully shared the Promise will resolve. If the user is unable to or chooses not to share, the Promise will reject.&lt;/p&gt;
&lt;p&gt;You can feature detect the existence of the API so you can progressively enhance a site with the web share API. This is good news right now as the API is only available as an &lt;a href=&quot;https://github.com/jpchase/OriginTrials/blob/gh-pages/developer-guide.md&quot;&gt;Origin Trial&lt;/a&gt;. This means that it is not widely available and you need to opt in to testing the feature on your site. If you are on a browser that doesn&apos;t yet support the API and you click &quot;share it&quot; at the bottom of this post, you just get directed to the &lt;a href=&quot;https://dev.twitter.com/web/tweet-button/web-intent&quot;&gt;Twitter web intent&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Implementing the web share API&lt;/h2&gt;
&lt;p&gt;This is how you&apos;d call the web share API on its own.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;navigator.share({ title: title, url: url })
  .then(function() { console.log(&quot;Share success!&quot;); })
  .catch(function() { console.log(&quot;Share failure!&quot;); });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is my first implementation of the web share API for this site.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var shareLinks = [].slice.call(document.querySelectorAll(&apos;.share&apos;));
shareLinks.forEach(function(link) {
  link.addEventListener(&apos;click&apos;, function (event) {
    if (typeof navigator.share !== &apos;undefined&apos;) {
      event.preventDefault();
      var canonicalElement = document.querySelector(&apos;link[rel=canonical]&apos;);
      if(canonicalElement !== undefined) {
        var url = canonicalElement.href;
      } else {
        var url = window.location.href;
      }
      var pageTitle = document.querySelector(&apos;.post-title&apos;).textContent;
      navigator.share({ title: pageTitle, url: url })
        .then(function() { console.log(&quot;Share success!&quot;); })
        .catch(function() { console.log(&quot;Share failure!&quot;); });
    } else {
      // No web share API...
    }
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, most of the code here is setting up listeners, feature checking and getting the actual data to share. Breaking it down, here&apos;s what happens:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select all links with the class &quot;share&quot;&lt;/li&gt;
&lt;li&gt;Add a &quot;click&quot; listener to each link&lt;/li&gt;
&lt;li&gt;When a share link is clicked, check for the existence of &lt;code&gt;navigator.share&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If it does exist, stop the link from doing anything&lt;/li&gt;
&lt;li&gt;Try to get the canonical URL for the page, first from the link element if it is present, otherwise from &lt;code&gt;window.location.href&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Get the page title from the page&apos;s h1 element&lt;/li&gt;
&lt;li&gt;Share the title and url&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can see &lt;a href=&quot;https://github.com/philnash/philna.sh/blob/3075d51dcf723b26eaae0fa1149dd5fa3a14b03e/_assets/js/main.js#L6-L49&quot;&gt;the full code (at the time of writing) in GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Thoughts on the API&lt;/h2&gt;
&lt;p&gt;I like the API and I like the benefits it brings to both developers and users.&lt;/p&gt;
&lt;p&gt;First up it&apos;s simple to implement. One method, 3 potential arguments, Promises for success and failure. You can dig a bit deeper into it, but on the surface a developer can have this implemented in a few minutes. Hopefully this simplicity will lead to swift adoption if the API becomes a standard.&lt;/p&gt;
&lt;p&gt;There&apos;s more reasons I&apos;m a fan though.&lt;/p&gt;
&lt;h3&gt;User choice&lt;/h3&gt;
&lt;p&gt;It stops host websites choosing where users can share content to. Sure, Facebook and Twitter are popular share destinations, but there are so many more apps out there that you could share content to. This long tail effect means that it is neither satisfying to only supply one or two share buttons, nor remotely sensible to make sharing everywhere possible. &lt;a href=&quot;http://platform.sharethis.com/get-inline-share-buttons&quot;&gt;ShareThis may have a bunch of share buttons available&lt;/a&gt;, but the selection still doesn&apos;t scratch the surface of possibility across the web.&lt;/p&gt;
&lt;p&gt;Ultimately, a share API like this puts the control back into the users&apos; hands, allowing them to share to the applications they choose to install on their device.&lt;/p&gt;
&lt;h3&gt;Minimising JavaScript&lt;/h3&gt;
&lt;p&gt;You&apos;ve seen the code above that I&apos;ve used to implement the web share API. There&apos;s not much of it.&lt;/p&gt;
&lt;p&gt;It&apos;s particularly small when you compare it to the scripts that popular sites supply to enable their share functionality. &lt;a href=&quot;https://dev.twitter.com/web/javascript/loading&quot;&gt;Twitter&apos;s script&lt;/a&gt; is ~32kB and &lt;a href=&quot;https://developers.facebook.com/docs/plugins/share-button&quot;&gt;Facebook&apos;s&lt;/a&gt; ~60kB (gzipped sizes). Best practice is to load those scripts asynchronously, so they shouldn&apos;t affect page load time. But that is a significant amount of JavaScript that needs to be loaded and parsed simply for a share button.&lt;/p&gt;
&lt;p&gt;These scripts don&apos;t just function as the share buttons you are implementing as a developer. They allow Facebook, Twitter and the like to track their users around the web. The web share API not only gives site owners a more efficient way for users to share, but also gives back some user privacy.&lt;/p&gt;
&lt;h3&gt;Better feedback&lt;/h3&gt;
&lt;p&gt;While it may make it harder for large social networks to track users, the web share API actually makes it easier for sites to learn about their users&apos; behaviour. As the share API works with Promises, you can find out when the share link is clicked and when a click results in a share to another application.&lt;/p&gt;
&lt;h2&gt;What&apos;s missing?&lt;/h2&gt;
&lt;p&gt;Currently the thing that is missing from the implementation of the web share API is the other side of it. &lt;a href=&quot;https://github.com/WICG/web-share-target&quot;&gt;The web share target API&lt;/a&gt; is an API that will allow web applications to register to appear in the share drawer when the web share API is invoked. Without the target API the web share API can only share directly to native applications available on a user&apos;s device. While this is probably still ok for those large social networks which already have applications installed on users&apos; devices, this doesn&apos;t help the long tail of potential share targets.&lt;/p&gt;
&lt;p&gt;I understand that becoming a share target is a much more complicated process than sharing. It is a good thing that the investigation of the share API and the share target API has been split up so that we can experiment with the web share API sooner. I hope to see the web share target API trialling ideas soon though.&lt;/p&gt;
&lt;h2&gt;Share away&lt;/h2&gt;
&lt;p&gt;The web share API is currently an experiment (&lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSfO0_ptFl8r8G0UFhT0xhV17eabG-erUWBDiKSRDTqEZ_9ULQ/viewform?entry.1999497328=Web+Share+(Experimenting+until+April+2017)&quot;&gt;which you can sign up to trial yourself&lt;/a&gt;) but I think it&apos;s an interesting and well meaning one. Ultimately giving users the choice over what they can share to is better for users and better for the long tail of applications.&lt;/p&gt;
&lt;p&gt;What do you think of the web share API? Let me know on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;. And if you&apos;re feeling generous, why not &amp;lt;a href=&quot;https://twitter.com/intent/tweet?text=The+web+share+API&amp;amp;url=https://philna.sh%2Fblog%2F2017%2F03%2F14%2Fthe-web-share-api%2F&amp;amp;via=philnash&quot; class=&quot;share&quot;&amp;gt;share this post&amp;lt;/a&amp;gt; too!&lt;/p&gt;
</description><pubDate>Tue, 14 Mar 2017 00:00:00 GMT</pubDate><category>web</category><category>javascript</category><category>share</category></item><item><title>Doing things wrong</title><link>https://philna.sh/blog/2017/02/13/doing-things-wrong/</link><guid isPermaLink="true">https://philna.sh/blog/2017/02/13/doing-things-wrong/</guid><description>&lt;p&gt;&lt;a href=&quot;https://philna.sh/blog/2017/02/09/toast-to-es2015-destructuring/&quot;&gt;I wrote a blog post last week&lt;/a&gt; and bits of it were wrong. This is not a retraction of that blog post, I just wanted to share the feedback that I got, the things I changed and some lessons that I learned.&lt;/p&gt;
&lt;h2&gt;Inconsistency sucks&lt;/h2&gt;
&lt;p&gt;The first feedback I received was from &lt;a href=&quot;https://www.reddit.com/r/javascript/comments/5szzfd/es2015_destructuring_promises_and_beer/&quot;&gt;posting the article on /r/javascript&lt;/a&gt;. The comment is deleted now so I can&apos;t credit the author, but they pointed out a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Code styles between my two examples were different&lt;/li&gt;
&lt;li&gt;Two semicolons were missing&lt;/li&gt;
&lt;li&gt;It was better to format chains of Promises by placing the &lt;code&gt;.then&lt;/code&gt; on the following line and indenting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I knew that the style of the two snippets was different as one had been written a year ago for the &lt;a href=&quot;http://12devsofxmas.co.uk/2016/01/day-9-service-worker-santas-little-performance-helper/&quot;&gt;12 Devs of Christmas blog&lt;/a&gt; and I had opted to retain the original code. But, mixing code styles in a project is unforgivable so why should a blog post be any different?&lt;/p&gt;
&lt;p&gt;The formatting issues were also obvious once they had been pointed out to me. So, I went back to the post, improved the &lt;a href=&quot;https://github.com/philnash/philna.sh/commit/9bd9af741934c5903d300c70e483d22f6de767cc&quot;&gt;Promise formatting&lt;/a&gt; and fixed both the &lt;a href=&quot;https://github.com/philnash/philna.sh/commit/707e0f9fe7724be999c3b381d34636042bc196ae&quot;&gt;semicolons and the code style&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Over promising under delivers&lt;/h2&gt;
&lt;p&gt;The second piece of feedback came over Twitter, firstly in a direct message from &lt;a href=&quot;https://twitter.com/elmasse&quot;&gt;Masse Fierro&lt;/a&gt; and then in a mention from &lt;a href=&quot;https://twitter.com/JamesDiGioia/status/830163324092481536&quot;&gt;James DiGioia&lt;/a&gt;. I&apos;d gotten paranoid with my Promises and written one or two too many. I just needed to return the final data and not resolve another Promise.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   const punkAPIPromise = fetch(punkAPIUrl)
     .then(res =&amp;gt; res.json())
-    .then(data =&amp;gt; Promise.resolve(data[0]));
+    .then(data =&amp;gt; data[0]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to Masse and James &lt;a href=&quot;https://github.com/philnash/philna.sh/commit/ea38977e47e60768f430cc5a00758d44cb0d15b6&quot;&gt;I fixed that too&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Accessibility matters&lt;/h2&gt;
&lt;p&gt;The last thing to be pointed out, and probably the most important, was not about the content of the blog post but the accessibility of my site in general. &lt;a href=&quot;https://twitter.com/varjmes/status/830416813091614720&quot;&gt;James Spencer pointed out that my site failed colour contrast tests&lt;/a&gt; for both the code samples and links on the site. The tests they referred to are the &lt;a href=&quot;https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html&quot;&gt;Web Content Accessibility Guidelines on colour contrast&lt;/a&gt; which recommend a contrast ratio of at least 4.5:1 for regular body text. I learned some interesting things trying to fix this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://khan.github.io/tota11y/&quot;&gt;tota11y&lt;/a&gt;, by the &lt;a href=&quot;https://www.khanacademy.org/&quot;&gt;Khan Academy&lt;/a&gt;, is a really awesome tool
&lt;ul&gt;
&lt;li&gt;I learned about it from the link James shared with me&lt;/li&gt;
&lt;li&gt;It suggests colours that are similar to the existing colour that would pass the test&lt;/li&gt;
&lt;li&gt;It also has other plugins that visualise things like out of order heading usage, poor link text, missing alt attributes and a few more, helping you to ensure your site is as accessible as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Syntax highlighting themes are really bad at accessible contrast levels
&lt;ul&gt;
&lt;li&gt;I was using a version of &lt;a href=&quot;http://ethanschoonover.com/solarized&quot;&gt;Solarized Dark&lt;/a&gt; which failed in a number of places&lt;/li&gt;
&lt;li&gt;I couldn&apos;t find any syntax highlighting theme that had decent contrast for all elements out of the box&lt;/li&gt;
&lt;li&gt;Using syntax highlighting in your editor is one thing, you can use whichever contrast levels you are comfortable with, but on the web anyone could be reading it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually I chose to use the suggestions from tota11y to &lt;a href=&quot;https://github.com/philnash/philna.sh/commit/60201a04397ad577f7e1b37809e157d3b5309c74&quot;&gt;fix the four colours in Solarized Dark that were too low contrast&lt;/a&gt;. I also fixed my link text colour.&lt;/p&gt;
&lt;p&gt;I was a little embarrassed about this because, &lt;a href=&quot;https://twitter.com/philnash/status/830421373776515074&quot;&gt;as I said in a tweet&lt;/a&gt;, I thought I used to be good at this sort of thing. So much so that almost 10 years ago I wrote a blog post on the very matter of &lt;a href=&quot;http://www.unintentionallyblank.co.uk/2007/09/27/web-accessibility-colour/&quot;&gt;colour in web accessibility&lt;/a&gt;. This just goes to show that not only do we learn new things every day in web development, retaining old things is just as important.&lt;/p&gt;
&lt;h2&gt;Always be improving&lt;/h2&gt;
&lt;p&gt;I want to thank Masse, the two Jameses and the now mystery Redditor for giving me the feedback that helped to improve both my blog post and my site. If you see anything on this site that could be improved I welcome the feedback. I learned that I should keep my eye on the formatting and consistency of my code, not to over promise and to pay closer attention to accessibility concerns.&lt;/p&gt;
&lt;p&gt;Overall I learned that in order to always be improving—my code, my site, myself—sometimes I need to do things wrong.&lt;/p&gt;
</description><pubDate>Mon, 13 Feb 2017 00:00:00 GMT</pubDate><category>learning</category><category>community</category><category>javascript</category></item><item><title>A toast to ES2015 destructuring</title><link>https://philna.sh/blog/2017/02/09/toast-to-es2015-destructuring/</link><guid isPermaLink="true">https://philna.sh/blog/2017/02/09/toast-to-es2015-destructuring/</guid><description>&lt;p&gt;I think the &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot;&gt;ES2015 destructuring syntax&lt;/a&gt; is pretty cool. It might not grab the headlines like Promises, Generators or the class syntax, but I find it really useful. It&apos;s also &lt;a href=&quot;http://exploringjs.com/es6/ch_destructuring.html&quot;&gt;surprisingly detailed when you read into it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The only shame is that most of the examples I&apos;ve found focus on just the syntax and not real life usage. Like this one from the &lt;a href=&quot;https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment&quot;&gt;MDN docs&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var a, b;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luckily I have a real life use case for destructuring for you right here.&lt;/p&gt;
&lt;h2&gt;Keeping Promises&lt;/h2&gt;
&lt;p&gt;One of my favourite uses for destructuring is when using &lt;code&gt;Promise.all&lt;/code&gt; to wait for a number of asynchronous tasks to complete. The result of &lt;code&gt;Promise.all&lt;/code&gt; is returned as an array and you would normally iterate over it or pick out the results you need by hand.&lt;/p&gt;
&lt;p&gt;But, if you know what objects you are expecting to receive you can use parameter destructuring to make your life easier and your code both prettier and more descriptive by naming the parameters up front. Let&apos;s see an example.&lt;/p&gt;
&lt;h2&gt;Comparing beers&lt;/h2&gt;
&lt;p&gt;Let&apos;s say you want to compare two of &lt;a href=&quot;https://www.brewdog.com/beer/headliners&quot;&gt;BrewDog&apos;s delicious beers&lt;/a&gt;, something I find myself doing in real life &lt;a href=&quot;https://untappd.com/user/philnash&quot;&gt;all the time&lt;/a&gt;. We can get the information about them from Sam Mason&apos;s gloriously named &lt;a href=&quot;https://punkapi.com/&quot;&gt;Punk API&lt;/a&gt;. To implement this we use the &lt;code&gt;fetch&lt;/code&gt; API to get the data on each beer from the API. We need both requests to resolve before we can compare the beers.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const punkAPIUrl = &quot;https://api.punkapi.com/v2/beers/106&quot;;
  const deadPonyClubUrl = &quot;https://api.punkapi.com/v2/beers/91&quot;;
  const punkAPIPromise = fetch(punkAPIUrl)
    .then(res =&amp;gt; res.json())
    .then(data =&amp;gt; data[0]);
  const deadPonyClubPromise = fetch(deadPonyClubUrl)
    .then(res =&amp;gt; res.json())
    .then(data =&amp;gt; data[0]);

  Promise.all([punkAPIPromise, deadPonyClubPromise])
    .then(beers =&amp;gt; {
      const punkIPA = beers[0];
      const deadPonyClub = beers[1];
      const stronger = (punkIPA.abv &amp;lt; deadPonyClub.abv ? deadPonyClub.name : punkIPA.name) + &quot; is stronger&quot;;
      console.log(stronger);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can tidy that Promise up with parameter destructuring:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  Promise.all([punkAPIPromise, deadPonyClubPromise])
    .then(([punkIPA, deadPonyClub]) =&amp;gt; {
      const stronger = (punkIPA.abv &amp;lt; deadPonyClub.abv ? deadPonyClub.name : punkIPA.name) + &quot; is stronger&quot;;
      console.log(stronger);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We know we&apos;re getting two beers as a result and the way this example is constructed we even know which beer is which. So we can use the parameter destructuring to name the beers in the array instead of plucking them out.&lt;/p&gt;
&lt;h2&gt;More examples&lt;/h2&gt;
&lt;p&gt;Ok, this may still seem like a made up example, but it certainly is closer to real life. The first time I found myself using this technique was when writing about &lt;a href=&quot;http://12devsofxmas.co.uk/2016/01/day-9-service-worker-santas-little-performance-helper/&quot;&gt;Service Workers for 12 Devs of Christmas&lt;/a&gt;. It came in handy when writing the method &lt;code&gt;returnFromCacheOrFetch&lt;/code&gt; which implements the &quot;stale while revalidate&quot; caching method using the &lt;code&gt;caches&lt;/code&gt; and &lt;code&gt;fetch&lt;/code&gt; APIs.&lt;/p&gt;
&lt;p&gt;The method opens up a named cache and tries to match the current request against the cache. Before returning, it then kicks off a &lt;code&gt;fetch&lt;/code&gt; request for the requested resource, caching the result. Finally if the request was found in the cache the cached response is returned, otherwise the new fetch request is returned. You can read more about it in &lt;a href=&quot;http://12devsofxmas.co.uk/2016/01/day-9-service-worker-santas-little-performance-helper/&quot;&gt;the original blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The final code looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function returnFromCacheOrFetch(request, cacheName) {
  const cachePromise = caches.open(cacheName);
  const matchPromise = cachePromise
    .then((cache) =&amp;gt; cache.match(request));

  // Use the result of both the above Promises to return the Response. Promise.all returns an array, but we destructure that in the callback.
  return Promise.all([cachePromise, matchPromise])
    .then(([cache, cacheResponse]) =&amp;gt; {
      // Kick off the update request
      const fetchPromise = fetch(request)
        .then((fetchResponse) =&amp;gt; {
          // Cache the updated file and then return the response
          cache.put(request, fetchResponse.clone());
          return fetchResponse;
        });
      // return the cached response if we have it, otherwise the result of the fetch.
      return cacheResponse || fetchPromise;
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case I needed both the result of &lt;code&gt;caches.open&lt;/code&gt; and &lt;code&gt;cache.match(request)&lt;/code&gt; in order to perform the background fetch and return the cached response. I drew them together using &lt;code&gt;Promise.all&lt;/code&gt; and destructured the resulting array keeping the code tidy and descriptive.&lt;/p&gt;
&lt;h2&gt;Naming things&lt;/h2&gt;
&lt;p&gt;In these examples, parameter destructuring allows us to name the results we expect to get from the resolved Promises we pass to &lt;code&gt;Promise.all&lt;/code&gt;. In fact, anywhere we use parameter destructuring, particularly with arrays, it allows us to name objects early and more descriptively. This in turn makes the code more readable and more maintainable in the long term.&lt;/p&gt;
&lt;p&gt;Are there other places in your code that you&apos;ve found the ES2015 destructuring syntax helpful? I&apos;d love to know how you use the feature too, so please share your destructuring tips with &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;me on Twitter&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 09 Feb 2017 00:00:00 GMT</pubDate><category>javascript</category><category>es2015</category><category>es6</category></item><item><title>On fixing a favicon</title><link>https://philna.sh/blog/2017/01/27/on-fixing-a-favicon/</link><guid isPermaLink="true">https://philna.sh/blog/2017/01/27/on-fixing-a-favicon/</guid><description>&lt;p&gt;Sometimes open source work is just fixing one tiny thing that bugs you. However, rolling up your sleeves and delving into even the smallest amount of code can lead to surprising results.&lt;/p&gt;
&lt;p&gt;I fixed a favicon recently. Here&apos;s what happened.&lt;/p&gt;
&lt;h2&gt;I fixed something for me&lt;/h2&gt;
&lt;p&gt;At the very least, fixing this issue has made me happier. The problem was that the &lt;a href=&quot;https://crystal-lang.org/api/0.20.5/&quot;&gt;Crystal standard library documentation&lt;/a&gt; wasn&apos;t showing a favicon, so when I had a bunch of tabs open in my browser I couldn&apos;t tell which ones had the Crystal docs I was using.&lt;/p&gt;
&lt;h2&gt;I fixed something for someone else&lt;/h2&gt;
&lt;p&gt;This is a guess, but if I found this an issue then we can presume at least one other person out there on the internet did.&lt;/p&gt;
&lt;h2&gt;I learned more about favicons&lt;/h2&gt;
&lt;p&gt;This was the bit I didn&apos;t expect. I&apos;ve built websites for a long time now, I thought I knew all there was to know about favicons. How wrong I was.&lt;/p&gt;
&lt;p&gt;I started by making a mistake. My &lt;a href=&quot;https://github.com/crystal-lang/crystal/pull/3832&quot;&gt;original pull request was against the Crystal project itself&lt;/a&gt; and added &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; elements to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the documentation&apos;s HTML layout. This would make it dependent on the structure of the Crystal website though.&lt;/p&gt;
&lt;p&gt;Feedback lead me to try again. This time I wanted to make the favicon for the Crystal website work by default for any pages situated on the crystal-lang.org domain, including the docs. This lead to a bunch of discoveries about favicons.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you have a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; for both a &lt;code&gt;.png&lt;/code&gt; and a &lt;code&gt;.ico&lt;/code&gt; favicon, &lt;a href=&quot;http://www.jonathantneal.com/blog/understand-the-favicon/&quot;&gt;Chrome and Safari will always pick the &lt;code&gt;.ico&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Firefox will always pick the &lt;code&gt;.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Internet Explorer only started supporting &lt;code&gt;.png&lt;/code&gt; favicons since version 11&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, if you want to use a high quality &lt;code&gt;.png&lt;/code&gt; favicon and a fallback for old Internet Explorer then the best course of action is to include a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; to the &lt;code&gt;.png&lt;/code&gt; icon in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and place the &lt;code&gt;.ico&lt;/code&gt; version in the default location: &lt;code&gt;/favicon.ico&lt;/code&gt;. This way Chrome, Safari, Firefox and IE11/Edge will all choose the &lt;code&gt;.png&lt;/code&gt; icon and old Internet Explorer will ignore the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; and look for &lt;code&gt;/favicon.ico&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Of course, as with anything on the web, &lt;a href=&quot;https://github.com/audreyr/favicon-cheat-sheet&quot;&gt;there is even more to it than that&lt;/a&gt;. However, this was &lt;a href=&quot;https://github.com/crystal-lang/crystal-website/pull/15/files&quot;&gt;my resulting pull request&lt;/a&gt; and it was merged. Now favicons work on the documentation as well as the main site.&lt;/p&gt;
&lt;h2&gt;I learned more about the Crystal project&lt;/h2&gt;
&lt;p&gt;In my early pull request I learned about how the Crystal standard library documentation is generated and rendered. Then, when exploring the &lt;a href=&quot;https://github.com/crystal-lang/crystal-website&quot;&gt;Crystal website&lt;/a&gt; I found that not only is it currently built on &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; but that there was an &lt;a href=&quot;https://github.com/crystal-lang/crystal-website/issues/12&quot;&gt;open issue&lt;/a&gt; pointing to my blog post on &lt;a href=&quot;https://philna.sh/blog/2016/06/28/asset-pipelines-with-jekyll-assets/&quot;&gt;using Jekyll Assets to build an asset pipeline&lt;/a&gt;. If there was one thing in the world that I was definitely capable of it was using my own blog post to make caching assets better. So I rolled up my sleeves and prepared &lt;a href=&quot;https://github.com/crystal-lang/crystal-website/pull/16&quot;&gt;another pull request&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now I am more familiar with the Crystal website and the team behind it that have commented on, reviewed and merged my pull requests.&lt;/p&gt;
&lt;h2&gt;The simplest piece of work can lead you anywhere&lt;/h2&gt;
&lt;p&gt;I just wanted to make favicons work yet it lead to 3 pull requests, a rabbit hole of discovery about favicons and browsers and the motivation to write a blog post about it. Ultimately, I did get what I wanted in the first place though: more recognisable tabs when I&apos;m working with Crystal.&lt;/p&gt;
&lt;p&gt;Open source is a lot of things to a lot of people. To me, it&apos;s a way to learn while I help, however small the task may seem. I&apos;d love for you to share with &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;me on Twitter&lt;/a&gt; what open source means to you.&lt;/p&gt;
</description><pubDate>Fri, 27 Jan 2017 00:00:00 GMT</pubDate><category>open source</category><category>favicon</category><category>crystal</category></item><item><title>Dev Tools Tricks: Store objects and elements as variables in the console</title><link>https://philna.sh/blog/2017/01/12/dev-tools-tricks-store-objects-and-elements-as-variables-in-the-console/</link><guid isPermaLink="true">https://philna.sh/blog/2017/01/12/dev-tools-tricks-store-objects-and-elements-as-variables-in-the-console/</guid><description>&lt;p&gt;Browser dev tools are so full of features it&apos;s hard to keep up. I bet every developer knows a different set of features to each other. I wanted to share a few little tips that I use, that you might not know and that if I don&apos;t write down I might forget.&lt;/p&gt;
&lt;p&gt;Read on and discover the magic of &lt;code&gt;$0&lt;/code&gt;, &lt;code&gt;$_&lt;/code&gt; and &lt;code&gt;temp0&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Access selected elements as variables&lt;/h2&gt;
&lt;p&gt;When you&apos;re clicking around the Inspector in Firefox or the Elements tab in Chrome and you want to use the selected element in the console, you need a reference to that element. You could type out a &lt;code&gt;querySelector&lt;/code&gt; that would pick the element out of the document, but that&apos;s far too much work. Instead, leave the element selected and use &lt;code&gt;$0&lt;/code&gt; in the console to get the reference you need.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/dev-tools-tricks/firefox-dollar-zero.png&quot; alt=&quot;Typing $0 in the Firefox dev tools console will grab a reference to the currently selected element.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Chrome even hints at this by showing &lt;code&gt;== $0&lt;/code&gt; next to the HTML.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/dev-tools-tricks/chrome-dollar-zero.png&quot; alt=&quot;Typing $0 in the Chrome dev tools console will grab a reference to the currently selected element.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Be aware though, if you select a different element in the inspector then it will become the subject of &lt;code&gt;$0&lt;/code&gt; and you will lose the reference to the old one.&lt;/p&gt;
&lt;p&gt;Firefox has another trick for this. Right click on an element and choose &quot;Use in console&quot;. This places a variable called &lt;code&gt;temp0&lt;/code&gt; into the console&apos;s command line which is a reference to that selected element.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/dev-tools-tricks/firefox-use-in-console.png&quot; alt=&quot;In Firefox you can right click on an element and choose &apos;Use in console&apos; to use it in the console as a variable named temp0.&quot; loading=&quot;lazy&quot;/&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Turn any object in the console into a global variable&lt;/h2&gt;
&lt;p&gt;If you&apos;re dealing with other objects in the console, perhaps something you&apos;ve logged from your own code, and you want to reference them then there are a couple of ways to do that too.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$_&lt;/code&gt;, for example, is a reference to the last object that was returned in the console.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/dev-tools-tricks/firefox-dollar-underscore&quot; alt=&quot;Typing $_ in the dev tools console will grab a reference to the last returned object in the console.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;More generally, any object that has been returned or logged to the console can be turned into a global variable by right clicking on it and selecting &quot;Store as global variable&quot;. You will get a variable called &lt;code&gt;temp0&lt;/code&gt; which references that object. Even better, do it for more objects and you&apos;ll get new variables called &lt;code&gt;temp1&lt;/code&gt;, then &lt;code&gt;temp2&lt;/code&gt; and so on.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/dev-tools-tricks/store-as-global.png&quot; alt=&quot;Right click on any object in the console and choose &apos;Store as global object&apos; to save it as a variable.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/picture&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;Dev tools go deep&lt;/h2&gt;
&lt;p&gt;There is so much to learn about dev tools, these are just a few little tricks that might help when debugging your code. If you&apos;re looking for more tips like this, I recommend signing up to &lt;a href=&quot;https://umaar.com/dev-tips/&quot;&gt;Umar Hansa&apos;s Dev Tips&lt;/a&gt; or watch his &lt;a href=&quot;https://www.youtube.com/watch?v=N33lYfsAsoU&amp;amp;list=PLXmT1r4krsTpDoGcdh1baZPIV6DtX9_rX&quot;&gt;ffconf 2016 talk&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Don&apos;t forget to share your own tips or things you&apos;ve found in dev tool. If you&apos;ve got any good ones, please send them to me on Twitter at &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;@philnash&lt;/a&gt;.&lt;/p&gt;
</description><pubDate>Thu, 12 Jan 2017 00:00:00 GMT</pubDate><category>dev tools</category><category>javascript</category><category>chrome</category><category>firefox</category></item><item><title>Git back to the future</title><link>https://philna.sh/blog/2017/01/04/git-back-to-the-future/</link><guid isPermaLink="true">https://philna.sh/blog/2017/01/04/git-back-to-the-future/</guid><description>&lt;p&gt;Git may be the best version control software I&apos;ve used but it is a complex beast and makes it easy to shoot yourself in the foot. Recently, however, I learned of one way that you can unshoot yourself and potentially save yourself hours of lost work.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-left&quot;&amp;gt;&amp;lt;a href=&quot;https://www.xkcd.com/1597/&quot;&amp;gt;&amp;lt;img src=&quot;https://imgs.xkcd.com/comics/git.png&quot; alt=&quot;XKCD&apos;s take on Git, in brief it is about memorising a bunch of commands to use and deleting everything if you mess up.&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git reset&lt;/code&gt; is useful when you&apos;ve done something wrong. You can turn back the clock and undo commits.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git reset --soft HEAD~1&lt;/code&gt; undoes one commit and leaves the work from that commit still present in the working directory.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git reset --hard HEAD~1&lt;/code&gt; removes the commit and all the work.&lt;/p&gt;
&lt;p&gt;The difference between the &lt;code&gt;--soft&lt;/code&gt; and &lt;code&gt;--hard&lt;/code&gt; flag is one of those ways you can shoot yourself in the foot. I know I&apos;ve mixed the two up and lost work. Or so I thought.&lt;/p&gt;
&lt;h2&gt;Enter the reflog&lt;/h2&gt;
&lt;p&gt;It turns out that git is watching us closer than we may think. When we make changes to any branch, git stores those changes in the reflog. Even if we were to remove a whole bunch of work by resetting with the &lt;code&gt;--hard&lt;/code&gt; flag, git still knows about the commits we had made and we can recover them. The key is that the commits still exist, there just aren&apos;t any branches that currently point to them. This is where the reflog comes into play. It has a log of all commits made in the repo, as well as other actions, and we can use it to recover these lost commits.&lt;/p&gt;
&lt;h3&gt;Warning!&lt;/h3&gt;
&lt;p&gt;Before I show you how this works, please note the following. If you have uncommitted changes in the working directory and you use &lt;code&gt;git reset --hard&lt;/code&gt; no amount of fancy git knowledge about the reflog is going to get that back. The following will save only work that you have committed. Be warned!&lt;/p&gt;
&lt;h2&gt;How to use the reflog to recover lost commits&lt;/h2&gt;
&lt;p&gt;Here&apos;s an example, I have a repo with one commit. Running &lt;code&gt;git log --oneline&lt;/code&gt; and &lt;code&gt;git reflog&lt;/code&gt; both show the commit has the hash &lt;code&gt;2daf3ba&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/reflog1.png&quot; alt=&quot;Both log and reflog show the same initial commit.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Now we make a commit, something important of course.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/reflog2.png&quot; alt=&quot;Making an important commit adds another entry to both the log and the reflog.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;What happens if we &lt;code&gt;git reset --hard HEAD~1&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/reflog3.png&quot; alt=&quot;When we hard reset the branch the log shows only our first commit, but the reflog shows everything.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git log&lt;/code&gt; shows only one commit, but &lt;code&gt;git reflog&lt;/code&gt; shows three actions; two commits and one reset. Note how the reset points to the same hash as the original commit. So, how do we get back to the last state before we reset? We can reset again.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/reflog4.png&quot; alt=&quot;We can reset the branch using the hash of the lost commit, the log then shows both our original commits.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;This time we reset using the hash of our lost commit. You can always reset using hashes, in fact &lt;code&gt;HEAD~1&lt;/code&gt; is really just a reference to a hash. What does the reflog look like now?&lt;/p&gt;
&lt;p&gt;&amp;lt;figure class=&quot;post-image post-image-outside&quot;&amp;gt;
&amp;lt;img src=&quot;/posts/reflog5.png&quot; alt=&quot;The reflog now shows the four actions  that were taken; two commits and then two resets.&quot; loading=&quot;lazy&quot;&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;The reflog now shows the four actions that we took; two commits and then two resets. Now we have reset the branch to the state we were in before, no work has been lost, breathe a sigh of relief.&lt;/p&gt;
&lt;h2&gt;May you never lose your work again&lt;/h2&gt;
&lt;p&gt;The git reflog keeps a track of everything we get up to with git. As with most things in git once you learn about the feature you can use it to your advantage. Knowing I have this trick up my sleeve has made me more confident using git.&lt;/p&gt;
&lt;p&gt;I&apos;d like to thank &lt;a href=&quot;https://twitter.com/tarkasteve&quot;&gt;Steve Smith&lt;/a&gt; for the talk he gave at &lt;a href=&quot;http://berlin2016.codemotionworld.com/&quot;&gt;Codemotion Berlin&lt;/a&gt; where I learned this trick. There is an &lt;a href=&quot;https://voicerepublic.com/talks/knowledge-is-power&quot;&gt;audio recording of his talk available&lt;/a&gt; and a much &lt;a href=&quot;https://www.atlassian.com/git/tutorials/refs-and-the-reflog/the-reflog&quot;&gt;more detailed article about git refs and the reflog on Atlassian&apos;s tutorial site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you need more tips for escaping from nightmare git scenarios, check out &lt;a href=&quot;http://ohshitgit.com/&quot;&gt;Oh shit, git!&lt;/a&gt; a collection of git tips from &lt;a href=&quot;https://twitter.com/ksylor&quot;&gt;Katie Sylor-Miller&lt;/a&gt;. She calls the reflog a magic time machine, I certainly agree and hopefully now you do too.&lt;/p&gt;
&lt;p&gt;&amp;lt;footer&amp;gt;
&amp;lt;small&amp;gt;Git logo by &amp;lt;a href=&quot;https://twitter.com/jasonlong&quot;&amp;gt;Jason Long&amp;lt;/a&amp;gt;.&amp;lt;/small&amp;gt;
&amp;lt;/footer&amp;gt;&lt;/p&gt;
</description><pubDate>Wed, 04 Jan 2017 00:00:00 GMT</pubDate><category>git</category><category>vcs</category></item><item><title>A community offers to help</title><link>https://philna.sh/blog/2016/09/05/a-community-offers-to-help/</link><guid isPermaLink="true">https://philna.sh/blog/2016/09/05/a-community-offers-to-help/</guid><description>&lt;p&gt;Last week I was blown away by the response to a problem I was experiencing from maintainers of two of the larger open source projects in the Ruby world. And not just your average large Ruby projects, but implementations of Ruby itself.&lt;/p&gt;
&lt;p&gt;I had &lt;a href=&quot;https://travis-ci.org/philnash/envyable/builds/156080057&quot;&gt;trouble running the tests&lt;/a&gt; for &lt;a href=&quot;https://github.com/philnash/envyable&quot;&gt;envyable&lt;/a&gt;, my &lt;a href=&quot;https://www.twilio.com/blog/2015/02/managing-development-environment-variables-across-multiple-ruby-applications.html&quot;&gt;gem to manage environment variables in your projects&lt;/a&gt;, on Rubinius on TravisCI. After installing the version of Rubinius locally, I couldn&apos;t reproduce the issue. So I tweeted.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;Anyone seen this error on &amp;lt;a href=&quot;https://twitter.com/travisci&quot;&amp;gt;@travisci&amp;lt;/a&amp;gt; with &amp;lt;a href=&quot;https://twitter.com/codeclimate&quot;&amp;gt;@codeclimate&amp;lt;/a&amp;gt; coverage on &amp;lt;a href=&quot;https://twitter.com/rubinius&quot;&amp;gt;@rubinius&amp;lt;/a&amp;gt;? &amp;lt;a href=&quot;https://t.co/fydiHoElCa&quot;&amp;gt;https://t.co/fydiHoElCa&amp;lt;/a&amp;gt;. I can&apos;t repro locally and am confused.&amp;lt;/p&amp;gt;— Phil Nash (@philnash) &amp;lt;a href=&quot;https://twitter.com/philnash/status/770542433901969409&quot;&amp;gt;August 30, 2016&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;Not only did &lt;a href=&quot;https://twitter.com/brixen&quot;&gt;Brian Shirai&lt;/a&gt;, maintainer of &lt;a href=&quot;http://rubinius.com/&quot;&gt;Rubinius&lt;/a&gt;, reach out to suggest some ideas for how to fix it. He also &lt;a href=&quot;https://github.com/rubinius/envyable/commit/fe574af226021f278b36f77278de3ad57768d7b2&quot;&gt;forked the project and tried to get it working himself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-conversation=&quot;none&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;&amp;lt;a href=&quot;https://twitter.com/philnash&quot;&amp;gt;@philnash&amp;lt;/a&amp;gt; you&apos;re using an ancient version 3.29. Ensure you&apos;re using Trusty distro and &apos;rbx&apos; in .travis.yml &amp;lt;a href=&quot;https://twitter.com/travisci&quot;&amp;gt;@travisci&amp;lt;/a&amp;gt; &amp;lt;a href=&quot;https://twitter.com/codeclimate&quot;&amp;gt;@codeclimate&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;— Rubinius (@rubinius) &amp;lt;a href=&quot;https://twitter.com/rubinius/status/770647800514093056&quot;&amp;gt;August 30, 2016&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;But that wasn&apos;t all. I followed his advice but eventually had to settle with moving Rubinius to the allowed failures list in my TravisCI config. I&apos;m still working with the TravisCI support team to try to fix the issue. We&apos;re getting there as it turns out &lt;a href=&quot;https://github.com/travis-ci/travis-rubies/commit/308d29198f0eeec87bf10d9110eb1df45c7dbb13&quot;&gt;TravisCI was looking in the wrong place for the latest Rubinius binaries&lt;/a&gt;. That issue is fixed but I am now getting &lt;a href=&quot;https://travis-ci.org/philnash/envyable/builds/157463309&quot;&gt;timeouts for my builds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This affected &lt;a href=&quot;http://jruby.org/&quot;&gt;JRuby&lt;/a&gt; too and when I mentioned that on Twitter, &lt;a href=&quot;https://twitter.com/headius&quot;&gt;Charles Nutter&lt;/a&gt; from the JRuby core team also offered to help out if it was a JRuby bug.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot; data-lang=&quot;en&quot;&amp;gt;&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;&amp;lt;a href=&quot;https://twitter.com/philnash&quot;&amp;gt;@philnash&amp;lt;/a&amp;gt; &amp;lt;a href=&quot;https://twitter.com/travisci&quot;&amp;gt;@travisci&amp;lt;/a&amp;gt; &amp;lt;a href=&quot;https://twitter.com/rubinius&quot;&amp;gt;@rubinius&amp;lt;/a&amp;gt; That JRuby failure looks like a timeout installing the base gemset in rvm. Happy to help if it is a JRuby bug.&amp;lt;/p&amp;gt;— Charles Nutter (@headius) &amp;lt;a href=&quot;https://twitter.com/headius/status/770792465955557376&quot;&amp;gt;August 31, 2016&amp;lt;/a&amp;gt;&amp;lt;/blockquote&amp;gt;&lt;/p&gt;
&lt;p&gt;My project is not the biggest or the most important Ruby project. Not by a long way. The issues are also likely not with Rubinius or JRuby. But the willingness for Brian and Charles to reach out to help was amazing. I&apos;m sure they are very busy people and just the response was appreciated.&lt;/p&gt;
&lt;h2&gt;Thank you&lt;/h2&gt;
&lt;p&gt;So thank you Brian and thank you Charles. Thank you for your work in the Ruby community and thank you specifically for offering your help to me. Acts like this maintain my belief in the power and the strength of the Ruby community.&lt;/p&gt;
</description><pubDate>Mon, 05 Sep 2016 00:00:00 GMT</pubDate><category>ruby</category><category>community</category></item><item><title>Install a service worker declaratively</title><link>https://philna.sh/blog/2016/08/17/install-a-service-worker-declaratively/</link><guid isPermaLink="true">https://philna.sh/blog/2016/08/17/install-a-service-worker-declaratively/</guid><description>&lt;p&gt;There&apos;s been some interesting updates in service workers recently. The big news is that the &lt;a href=&quot;http://www.ghacks.net/2016/08/14/microsoft-edge-improves-on-windows-10-14901/&quot;&gt;Microsoft Edge development version now has service workers, alongside push notifications and background sync, behind flags&lt;/a&gt;. There was a new feature that caught my eye though; a declarative method for registering service workers.&lt;/p&gt;
&lt;p&gt;Up until now, to register a service worker, you need to write JavaScript. It would look something like this (emoji logging optional):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  if (&apos;serviceWorker&apos; in navigator) {
    navigator.serviceWorker.register(&quot;/sw.js&quot;, { scope: &quot;/blog/&quot; })
      .then(function(registration) { console.log(&quot;🍻&quot;); })
      .catch(function(error) { console.log(&quot;😭&quot;, error); });
  }
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you allow the service worker lifecycle to play out naturally and you&apos;re not using &lt;a href=&quot;https://www.twilio.com/blog/2016/02/web-powered-sms-inbox-with-service-worker-push-notifications.html&quot;&gt;push notifications&lt;/a&gt; or background sync then this is all you need. New in Chrome 54 (currently Canary) there is an experiment to make this even simpler, more declarative, with the use of a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag in the HTML or via a &lt;code&gt;Link&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;I&apos;ve put together a quick example repository showing all three approaches which you can use to install a service worker. There&apos;s the JavaScript way, as seen above. Or, &lt;a href=&quot;https://github.com/philnash/install-service-worker/blob/master/public/link/index.html#L5&quot;&gt;you can just use a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag&lt;/a&gt;, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;serviceworker&quot; href=&quot;/sw.js&quot; scope=&quot;/blog/&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, you can &lt;a href=&quot;https://github.com/philnash/install-service-worker/blob/master/index.js#L5&quot;&gt;install the service worker with an HTTP header&lt;/a&gt; that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Link: &amp;lt;/sw.js&amp;gt;; rel=&quot;serviceworker&quot;; scope=&quot;/blog/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each of these methods will install the service worker file &lt;code&gt;/sw.js&lt;/code&gt; with the scope &lt;code&gt;&quot;/blog/&quot;&lt;/code&gt;. I&apos;ve produced an example of &lt;a href=&quot;https://github.com/philnash/install-service-worker&quot;&gt;how you&apos;d use these methods to install a service worker on GitHub&lt;/a&gt;. Make sure to enable &quot;Experimental Web Platform features&quot; in Chrome Canary before you try it out.&lt;/p&gt;
&lt;h2&gt;Foreign fetch&lt;/h2&gt;
&lt;p&gt;It turns out that these alternative ways to register a service worker stem from the foreign fetch proposal.&lt;/p&gt;
&lt;p&gt;When you have a service worker registered it can respond to any fetch request made from a page under its control. Foreign fetch intends to make it possible to register a service worker and respond to cross origin requests. You can read more about the &lt;a href=&quot;https://github.com/slightlyoff/ServiceWorker/blob/master/foreign_fetch_explainer.md&quot;&gt;foreign fetch proposal in the spec on GitHub&lt;/a&gt;. The take away for me was that foreign fetch will make it more robust to rely on third party APIs that you call from the browser in our web apps.&lt;/p&gt;
&lt;p&gt;As you can imagine, cross origin requests for resources like an API won&apos;t be running your regular JavaScript that would install your service worker. This is where the &lt;code&gt;Link&lt;/code&gt; header comes in.&lt;/p&gt;
&lt;h2&gt;The service worker revolution marches on&lt;/h2&gt;
&lt;p&gt;Whether it&apos;s new developments in browsers that already support service workers or new browsers joining the fold (come on Safari…), I continue to be excited about the potential this technology is bringing to our web applications. I particularly like simplifications to the process and installing a service worker with just a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag appeals to me.&lt;/p&gt;
&lt;p&gt;If you want to catch me speaking about my excitement for service workers I&apos;ll be doing so all over Europe this Autumn. Look out for me at &lt;a href=&quot;https://frontendconf.ch/speakers/#phil-nash&quot;&gt;Frontend Conference Zurich&lt;/a&gt;, &lt;a href=&quot;http://refresh.rocks/speakers#phil&quot;&gt;Refresh in Tallinn&lt;/a&gt; and &lt;a href=&quot;http://devday.pl/&quot;&gt;DevDay in Kraków&lt;/a&gt;. I hope to see you there!&lt;/p&gt;
</description><pubDate>Wed, 17 Aug 2016 00:00:00 GMT</pubDate><category>service worker</category><category>javascript</category></item><item><title>August is iOS month</title><link>https://philna.sh/blog/2016/08/01/august-is-ios-month/</link><guid isPermaLink="true">https://philna.sh/blog/2016/08/01/august-is-ios-month/</guid><description>&lt;p&gt;June saw me &lt;a href=&quot;/blog/2016/06/20/critique-my-swift-on-exercism/&quot;&gt;ask for your help&lt;/a&gt; as I started to learn Swift. July was busy with travel. Now August is iOS month.&lt;/p&gt;
&lt;p&gt;I&apos;m lucky that part of my job as a developer evangelist for &lt;a href=&quot;https://www.twilio.com/&quot;&gt;Twilio&lt;/a&gt; is to learn new things. That is why I attended the &lt;a href=&quot;https://training.bignerdranch.com/classes/ios-essentials-with-swift&quot;&gt;Big Nerd Ranch Swift and iOS course&lt;/a&gt; in June and is what has lead me to dedicate August to building my first iOS application.&lt;/p&gt;
&lt;h2&gt;My first app&lt;/h2&gt;
&lt;p&gt;By first application, I really mean my first one that I am going to take from beginning to end, from blank Xcode project to the App Store, all by myself. I have worked on iOS applications before. I remember a time when I managed to crash an iPad app so badly while working on it, that checking out the previous version of the app still lead to crashes in the feature that I&apos;d spent too long looking at. I did not receive too many more tasks to complete on that application. But those were the days of memory managed Objective C and working in a team.&lt;/p&gt;
&lt;p&gt;Now, with my newly gained knowledge of Swift, it&apos;s just me, Xcode and a month of clear space to build something.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/new_xcode_project.png&quot; alt=&quot;The view of Xcode when you start a new project.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;2,000,000 applications&lt;/h2&gt;
&lt;p&gt;Of course, the question one always lands on is what that something should be. This is my first application and a solo effort at that, so I&apos;m looking for something with a constrained scope, something that will be achievable this month, something that will be achievable by me. When I remembered that Apple announced at WWDC this year that the App Store now had &lt;a href=&quot;http://www.theverge.com/2016/6/13/11922926/apple-apps-2-million-wwdc-2016&quot;&gt;more than 2 million applications available to download&lt;/a&gt; I realised that I&apos;m not looking for a brand new idea. Anything that can be built by a novice developer within a month has almost certainly already been built.&lt;/p&gt;
&lt;p&gt;I decided the best idea would be to tackle something I have done before in a different environment. As it happens, I own &lt;a href=&quot;http://fxrat.es/&quot;&gt;fxrat.es&lt;/a&gt;, the world&apos;s least popular exchange rate calculator. It&apos;s best feature is not up to the minute exchange rate data, but that it works offline in modern browsers. It&apos;s a web application I wanted to use myself when abroad.&lt;/p&gt;
&lt;h2&gt;New and improved&lt;/h2&gt;
&lt;p&gt;I have been thinking about rewriting it for a couple of reasons. Firstly, sometimes it just doesn&apos;t work and I&apos;ve never found out why. Secondly, it currently works offline using the web platform&apos;s Application Cache. The AppCache is going away, &lt;a href=&quot;http://alistapart.com/article/application-cache-is-a-douchebag&quot;&gt;for good reasons&lt;/a&gt;, so it is time to update to use my new favourite browser feature, the Service Worker. However that will not work in Safari.&lt;/p&gt;
&lt;p&gt;This seems to me like a good opportunity to both rewrite my existing application for the web and produce a new, offline capable native application for iOS that I can release in the App Store.&lt;/p&gt;
&lt;h2&gt;The task is set&lt;/h2&gt;
&lt;p&gt;So that is August for me. Buried in Xcode creating a new application. Wish me luck and follow &lt;a href=&quot;https://github.com/philnash/fxrates-ios&quot;&gt;my progress on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See you in the App Store.&lt;/p&gt;
</description><pubDate>Mon, 01 Aug 2016 00:00:00 GMT</pubDate><category>swift</category><category>ios</category></item><item><title>Creating an asset pipeline with Jekyll-Assets</title><link>https://philna.sh/blog/2016/06/28/asset-pipelines-with-jekyll-assets/</link><guid isPermaLink="true">https://philna.sh/blog/2016/06/28/asset-pipelines-with-jekyll-assets/</guid><description>&lt;p&gt;When I started again on this site, I wanted to make sure it was going to load fast. &lt;a href=&quot;https://twitter.com/search?q=%23perfmatters&quot;&gt;Performance matters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bundling, minifying and caching the static assets was high on the priority list. The site is built on &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; so I went looking for (and ultimately found) a plugin that would perform the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bundle static files together&lt;/li&gt;
&lt;li&gt;Minify the resulting files&lt;/li&gt;
&lt;li&gt;Generate a hash of the content of the file as the production filename (&lt;a href=&quot;http://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care-questionmark&quot;&gt;the Rails documentation has a good explanation of this fingerprinting technique&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Can&apos;t Jekyll do this?&lt;/h2&gt;
&lt;p&gt;Jekyll 3 can handle the bundling and minification of CSS. In fact, Jekyll comes with &lt;a href=&quot;http://sass-lang.com/&quot;&gt;Sass&lt;/a&gt; included which actually does the heavy lifting.&lt;/p&gt;
&lt;p&gt;When you start a new Jekyll site, you will find a &lt;code&gt;css&lt;/code&gt; directory with a &lt;code&gt;main.css&lt;/code&gt; file present. That file then uses Sass imports to require other files from the &lt;code&gt;_sass&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;That&apos;s bundling handled already, how about minification? It takes one config update to enable that too. Add the following to your &lt;code&gt;_config.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sass:
  style: compressed
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Jekyll-Assets for all the features&lt;/h2&gt;
&lt;p&gt;If you&apos;re hosting a Jekyll site on &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; this is as good as it gets. You can&apos;t install other Jekyll plugins and GitHub handles the caching headers for your static files.&lt;/p&gt;
&lt;p&gt;But what if you have your own server and want more control? What if you want those fingerprints so that you can set your own cache headers far into the future? What if you want to provide your own transpilers, processors or minifiers?&lt;/p&gt;
&lt;p&gt;Those are the features I wanted. So I did a bit of research and found &lt;a href=&quot;https://jekyll.github.io/jekyll-assets/&quot;&gt;Jekyll-Assets&lt;/a&gt;. It&apos;s a pretty powerful plugin, with lots of features for optimising assets. Here&apos;s how you can get started using it.&lt;/p&gt;
&lt;h2&gt;Getting started with Jekyll-Assets&lt;/h2&gt;
&lt;p&gt;&amp;lt;div class=&quot;info&quot;&amp;gt;
&amp;lt;p&amp;gt;This blog post was written for Jekyll Assets version 2.4.0. Jekyll Assets version 3 has now been released and changes a few things, if you are using Jekyll Assets 3 the following may not apply.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s set up Jekyll-Assets for a Jekyll site. First up, we need to install the jekyll-assets gem. Add it to the &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gem &quot;jekyll-assets&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and install with &lt;code&gt;bundle install&lt;/code&gt;. Then add it to the &lt;code&gt;_config.yml&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gems:
  - jekyll-paginate
  - jekyll-assets
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jekyll-Assets comes with a comprehensive &lt;a href=&quot;https://github.com/jekyll/jekyll-assets/wiki/Configuration#development-defaults&quot;&gt;default config&lt;/a&gt; to make it quick to get started, so we don&apos;t need to change that right now. We do need to move a couple of files about though.&lt;/p&gt;
&lt;p&gt;As we saw earlier, Jekyll comes with a &lt;code&gt;css&lt;/code&gt; directory containing a &lt;code&gt;main.css&lt;/code&gt; file and then a &lt;code&gt;_sass&lt;/code&gt; directory with a few Sass imports. We want to move them all to the &lt;code&gt;_assets/css&lt;/code&gt; directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir -p _assets/css
$ mv css/main.css _assets/css
$ mv _sass/* _assets/css
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Jekyll includes some empty &lt;a href=&quot;https://jekyllrb.com/docs/frontmatter/&quot;&gt;front matter&lt;/a&gt; in &lt;code&gt;main.css&lt;/code&gt;. We need to get rid of that too.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
# Only the main Sass file needs front matter (the dashes are enough)
---
@charset &quot;utf-8&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It may &lt;em&gt;say&lt;/em&gt; that the main Sass file needs front matter but with Jekyll-Assets that is no longer the case.&lt;/p&gt;
&lt;h3&gt;Fixing up the HTML&lt;/h3&gt;
&lt;p&gt;The CSS is now no longer where the layout thinks it should be. We need to update the &lt;code&gt;_includes/head.html&lt;/code&gt; to point to our new CSS file. As we are going to be generating unique filenames for our assets in production we need to use the Jekyll-Assets supplied liquid tags to fill them in.&lt;/p&gt;
&lt;p&gt;Open up &lt;code&gt;_includes/head.html&lt;/code&gt; and replace the existing &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{ &quot;{% css main &quot; }}%}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to see the filename digests working a quick config change is all you need. Open up &lt;code&gt;_config.yml&lt;/code&gt; and add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;assets:
  digest: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the Jekyll development server with &lt;code&gt;bundle exec jekyll serve&lt;/code&gt; and open up &lt;a href=&quot;http://localhost:4000&quot;&gt;http://localhost:4000&lt;/a&gt;. The site will be displaying as you expect and if you view source you will see the generated &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag looking a little like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;/assets/main-daf4744048a36abfd0aae160e2f7c309c4ae468f16d65e77d89c70fc8d7ba6ec.css&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our digests are working! You can turn this off in development mode, Jekyll-Assets will generate digests, as well as bundle and minify, by default in production mode.&lt;/p&gt;
&lt;h3&gt;Other assets&lt;/h3&gt;
&lt;p&gt;If you want other assets to get the Jekyll-Assets treatment, all you need to do is move them to their respective directory in &lt;code&gt;_assets&lt;/code&gt; and refer to them using the relevant &lt;a href=&quot;https://jekyll.github.io/jekyll-assets/#tags&quot;&gt;liquid tags&lt;/a&gt;. The default available directories include &lt;code&gt;_assets/images&lt;/code&gt; and &lt;code&gt;_assets/js&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;On the server&lt;/h3&gt;
&lt;p&gt;The entire purpose of adding Jekyll-Assets to my site was to set cache headers way into the future so that browsers can cache the assets as aggressively as they can. The easiest part of this turned out to be adding the config to my web server. I&apos;m using nginx and all I needed was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
  # other config

  location /assets/ {
    expires 1y;
    add_header Cache-Control &quot;public&quot;;
  }

  # ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Bundled, minified, digested, cached&lt;/h2&gt;
&lt;p&gt;Using the Jekyll-Assets gem and a bit of nginx config I optimised the static assets on this site. There are loads more options for the gem including integrations with Babel for ES2015, CSS Autoprefixer and Image Magick for image optimisation. It is worth exploring &lt;a href=&quot;https://github.com/jekyll/jekyll-assets&quot;&gt;the code&lt;/a&gt; and the &lt;a href=&quot;https://jekyll.github.io/jekyll-assets/&quot;&gt;documentation&lt;/a&gt; to see what else can be achieved.&lt;/p&gt;
&lt;p&gt;Now my assets are nicely optimised it&apos;s more fun to run &lt;a href=&quot;http://www.webpagetest.org/&quot;&gt;WebPageTest&lt;/a&gt; against the site. Looks like I better start looking into CDN options now. Thankfully Jekyll-Assets supports those too.&lt;/p&gt;
&lt;p&gt;*[CDN]: Content Delivery Network
*[CSS]: Cascading Style Sheets
*[ES2015]: ECMAScript 2015
*[HTML]: Hypertext Markup Language&lt;/p&gt;
</description><pubDate>Tue, 28 Jun 2016 00:00:00 GMT</pubDate><category>jekyll</category><category>assets</category><category>ruby</category></item><item><title>Critique my Swift on Exercism</title><link>https://philna.sh/blog/2016/06/20/critique-my-swift-on-exercism/</link><guid isPermaLink="true">https://philna.sh/blog/2016/06/20/critique-my-swift-on-exercism/</guid><description>&lt;p&gt;I am currently in the middle of a Swift and iOS course with the excellent &lt;a href=&quot;https://training.bignerdranch.com/classes/ios-essentials-with-swift&quot;&gt;Big Nerd Ranch&lt;/a&gt;. Taking a week to dedicate to learning a new language and framework means I can block out all other distractions and really focus. And as this goes on, I&apos;d like your help.&lt;/p&gt;
&lt;p&gt;Specifically, I&apos;d like you to &lt;a href=&quot;http://exercism.io/profiles/philnash/748b8c235ed14663b82271f6b6e0a3e3&quot;&gt;log on to Exercism and critique my code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Practice makes…?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://exercism.io/&quot;&gt;Exercism&lt;/a&gt; is a collaborative tool for learning, practicing and reviewing code created by the fantastic &lt;a href=&quot;https://twitter.com/kytrinyx&quot;&gt;Katrina Owen&lt;/a&gt;. It&apos;s great for beginners, to a new language or to coding in general, to try out new skills and get reviews and tips from peers. It&apos;s also great for experienced programmers to keep sharp by writing and reading code. &lt;a href=&quot;https://github.com/exercism&quot;&gt;And it&apos;s all open source&lt;/a&gt; and has a great page on &lt;a href=&quot;http://exercism.io/contribute&quot;&gt;how to contribute&lt;/a&gt;, which is awesome.&lt;/p&gt;
&lt;p&gt;I&apos;m currently trying to use it to kickstart my Swift career. That&apos;s where you come in.&lt;/p&gt;
&lt;p&gt;Over the coming days and weeks I will be completing the &lt;a href=&quot;http://exercism.io/languages/swift&quot;&gt;Swift problems&lt;/a&gt; set out by Exercism and I&apos;d really like a second (or third, or fourth) opinion. If you know Swift ,or if you don&apos;t, all opinions are welcome, I would love it if you would &lt;a href=&quot;http://exercism.io/profiles/philnash/748b8c235ed14663b82271f6b6e0a3e3&quot;&gt;check out my attempts&lt;/a&gt; and leave some pointers on how I might improve.&lt;/p&gt;
&lt;h2&gt;Hello, World!&lt;/h2&gt;
&lt;p&gt;Just to kick it off, here&apos;s my &quot;Hello, World!&quot; from the &lt;a href=&quot;http://exercism.io/exercises/swift/hello-world/readme&quot;&gt;first Exercism challenge in Swift&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class HelloWorld {
    static func hello(name: String = &quot;World&quot;) -&amp;gt; String {
        return &quot;Hello, \(name)!&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It may only be five lines of code, but it&apos;s a start!&lt;/p&gt;
&lt;h2&gt;Improving together&lt;/h2&gt;
&lt;p&gt;This works better if we learn and feedback to each other, so I&apos;d love to help you too! If you&apos;re using Exercism to practice or learn something new, I am available to send feedback. If you want me to take a look at your code just give me a nudge on &lt;a href=&quot;https://twitter.com/philnash&quot;&gt;Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Exercism is a great tool and an opportunity for us all to improve together. So what are you waiting for?&lt;/p&gt;
</description><pubDate>Mon, 20 Jun 2016 00:00:00 GMT</pubDate><category>swift</category></item><item><title>The surprise multipart/form-data</title><link>https://philna.sh/blog/2016/06/13/the-surprise-multipart-form-data/</link><guid isPermaLink="true">https://philna.sh/blog/2016/06/13/the-surprise-multipart-form-data/</guid><description>&lt;p&gt;Building up and sending an Ajax request is so much easier than it ever used to be. No longer must we hassle ourselves with &lt;code&gt;XMLHttpRequest&lt;/code&gt;, never mind the horror of ActiveX&apos;s &lt;code&gt;ActiveXObject(&quot;Microsoft.XMLHTTP&quot;)&lt;/code&gt;. The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch&quot;&gt;Fetch API&lt;/a&gt; is here (and it is &lt;a href=&quot;https://github.com/github/fetch&quot;&gt;polyfilled&lt;/a&gt; for older browsers). Then there&apos;s the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects&quot;&gt;&lt;code&gt;FormData&lt;/code&gt; object&lt;/a&gt; that makes building up and submitting form data really easy, especially compared to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#A_little_vanilla_framework&quot;&gt;the 130 or so lines of JavaScript you&apos;d need to do it yourself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But you can still get things wrong. &lt;em&gt;I&lt;/em&gt; can definitely get things wrong.&lt;/p&gt;
&lt;p&gt;So I wanted to write about what I got wrong to remind myself for the future. My mistakes involved the Fetch API, &lt;code&gt;FormData&lt;/code&gt; and a simple &lt;a href=&quot;http://expressjs.com/&quot;&gt;Express&lt;/a&gt; server in Node.js.&lt;/p&gt;
&lt;h2&gt;I couldn&apos;t POST any data&lt;/h2&gt;
&lt;p&gt;It was simple, I wanted to POST some data to my server. I had Express set up, I&apos;d installed &lt;a href=&quot;https://github.com/expressjs/body-parser&quot;&gt;body-parser&lt;/a&gt; (normally something I forget to do until this kind of problem comes along), I had a route that was ready to receive my data and do something with it.&lt;/p&gt;
&lt;p&gt;It looked a little like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Express = require(&quot;express&quot;);
const bodyParser = require(&quot;body-parser&quot;);

const app = new Express();

app.use(bodyParser.urlencoded({ extended: true }));

app.post(&quot;/&quot;, function(request, response) {
  console.log(request.body.foo);
  response.send(&quot;OK&quot;);
});

app.listen(3000, function() {
  console.log(&quot;Application is listening on localhost:3000&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, it was more complicated than that, but that&apos;s all we need to demonstrate this.&lt;/p&gt;
&lt;p&gt;The front end was going to be even easier.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var formData = new FormData();
formData.append(&quot;foo&quot;, &quot;bar&quot;);
fetch(&quot;/&quot;, {
  method: &quot;POST&quot;,
  body: formData
}).then(function(response) {
  // Yay!
}).catch(function(err) {
  // Boo!
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plan was simple: run the server, run the front end code, see &quot;bar&quot; printed in the console, celebrate.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/formdata/undefined.png&quot; alt=alt=&apos;The console logged out &quot;undefined&quot;&apos; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;undefined&lt;/code&gt;, the JavaScript developer&apos;s worst enemy (aside from perhaps callback hell and developers from other languages telling them that they&apos;re doing it wrong).&lt;/p&gt;
&lt;p&gt;I searched up and down the &lt;code&gt;request&lt;/code&gt; object, looking for that simple piece of data that I had sent. I checked in all the places I thought it might be &lt;code&gt;request.body&lt;/code&gt;, &lt;code&gt;request.params&lt;/code&gt;, &lt;code&gt;request.query&lt;/code&gt;, Express has all of those you know. I checked in plenty of places I didn&apos;t think it would be. Finally I did what any sensible developer would do. I asked the internet.&lt;/p&gt;
&lt;h2&gt;The internet is a wonderful support network&lt;/h2&gt;
&lt;p&gt;I love that out there is a resource where I can ask questions and people respond and help me. The internet came up trumps in this case, I asked and many people chimed in with ways to fix the issue.&lt;/p&gt;
&lt;p&gt;The first solution was to set the content type to JSON and just send JSON to the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var data = { foo: &quot;bar&quot; };
fetch(&quot;/&quot;, {
  method: &quot;POST&quot;,
  body: JSON.stringify(data),
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;
  }
}).then(function(response) {
  // Yay!
}).catch(function(err) {
  // Boo!
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked, but I was confused. I had this really easy &lt;code&gt;FormData&lt;/code&gt; object to work with, but the solution was to throw it away and use &lt;code&gt;JSON.stringify&lt;/code&gt;? It didn&apos;t seem right. It worked but I wanted to persist and make it work the way I had intended.&lt;/p&gt;
&lt;h2&gt;It works in Rails&lt;/h2&gt;
&lt;p&gt;Eventually &lt;a href=&quot;https://twitter.com/edds&quot;&gt;Edd&lt;/a&gt; sorted me out. Firstly he said that my front end JavaScript wasn&apos;t wrong, the same code worked with a Rails back end. I think that put him on the route to find &lt;a href=&quot;http://stackoverflow.com/questions/36918287/cant-post-data-using-javascript-fetch-api&quot;&gt;the correct answer&lt;/a&gt; though.&lt;/p&gt;
&lt;p&gt;It turns out that body-parser doesn&apos;t understand &lt;code&gt;multipart/form-data&lt;/code&gt; encoded forms (though as Edd pointed out, &lt;a href=&quot;http://guides.rubyonrails.org/form_helpers.html#uploading-files&quot;&gt;Rails does&lt;/a&gt;). And &lt;code&gt;FormData&lt;/code&gt;, when added to a Fetch API request (or an &lt;code&gt;XMLHttpRequest&lt;/code&gt; as it happens, I checked), will set the content type to &lt;code&gt;multipart/form-data&lt;/code&gt;. It actually says that right here in the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects&quot;&gt;first paragraph of the MDN documentation&lt;/a&gt;. In bold.&lt;/p&gt;
&lt;p&gt;I like to say that &quot;people don&apos;t read things on the internet&quot;. &lt;em&gt;I&lt;/em&gt; don&apos;t read things on the internet.&lt;/p&gt;
&lt;p&gt;In my defence, I would have thought that body-parser might parse the body of the request. Once again skimming over the documentation was my downfall. Check out &lt;a href=&quot;https://www.npmjs.com/package/body-parser#readme&quot;&gt;line 2 of the body-parser readme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In fact, the body-parser readme doesn&apos;t just say that it won&apos;t handle &lt;code&gt;multipart/form-data&lt;/code&gt; bodies, it even goes as far as suggesting other modules to use for those requests.&lt;/p&gt;
&lt;p&gt;I ended up using &lt;a href=&quot;https://www.npmjs.com/package/formidable#readme&quot;&gt;formidable&lt;/a&gt; via the &lt;a href=&quot;https://www.npmjs.com/package/express-formidable&quot;&gt;express-formidable middleware&lt;/a&gt;. Our example server now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Express = require(&quot;express&quot;);
const bodyParser = require(&quot;body-parser&quot;);
const formidable = require(&quot;express-formidable&quot;);

const app = new Express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(formidable());

app.post(&quot;/&quot;, function(request, response) {
  console.log(request.fields.foo)
  response.send(&quot;OK&quot;);
});

app.listen(3000, function() {
  console.log(&quot;Application is listening on localhost:3000&quot;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Lessons learned&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;FormData&lt;/code&gt; objects set the content type of a Fetch API (or &lt;code&gt;XMLHttpRequest&lt;/code&gt;) request to &lt;code&gt;multipart/form-data&lt;/code&gt; automagically.&lt;/p&gt;
&lt;p&gt;body-parser doesn&apos;t support parsing the body of a &lt;code&gt;multipart/form-data&lt;/code&gt; request. But there are &lt;a href=&quot;https://www.npmjs.org/package/busboy#readme&quot;&gt;many&lt;/a&gt; &lt;a href=&quot;https://www.npmjs.org/package/multiparty#readme&quot;&gt;options&lt;/a&gt; &lt;a href=&quot;https://www.npmjs.org/package/formidable#readme&quot;&gt;that&lt;/a&gt; &lt;a href=&quot;https://www.npmjs.org/package/multer#readme&quot;&gt;do&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The internet is a very helpful place. Thank you to Edd and others who suggested what I might be doing wrong.&lt;/p&gt;
&lt;p&gt;READ THE DOCUMENTATION. Then probably read it again just to make sure.&lt;/p&gt;
&lt;h2&gt;There are no surprises&lt;/h2&gt;
&lt;p&gt;There are actually very few surprises in development, particularly when using modern browser APIs and well supported frameworks and reading the documentation. I learned quite a bit from this one small episode as you can see above. The next thing I need to do is write some documentation for someone else. Eventually, they&apos;ll read it and be thankful as I was.&lt;/p&gt;
</description><pubDate>Mon, 13 Jun 2016 00:00:00 GMT</pubDate><category>javascript</category><category>node</category><category>express</category></item><item><title>This site isn&apos;t finished</title><link>https://philna.sh/blog/2016/05/18/this-site-isnt-finished/</link><guid isPermaLink="true">https://philna.sh/blog/2016/05/18/this-site-isnt-finished/</guid><description>&lt;p&gt;No running site is ever completely finished. This one is no exception.&lt;/p&gt;
&lt;p&gt;There is more to build, more to style and more to write. It&apos;s a challenge.&lt;/p&gt;
&lt;p&gt;Welcome to the new version of my site. The old one was notable for it&apos;s long service and disappointing content. It&apos;s better left forgotten.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure&amp;gt;
&amp;lt;img src=&quot;/posts/old_site.png&quot; alt=&quot;The old version of this site was in a strange red and cream colour scheme. I&apos;m trying to forget it.&quot; loading=&quot;lazy&quot; /&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This site is finished.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So, here&apos;s to a fresh site, new writing and a place to experiment with the web.
I&apos;m quite looking forward to it.&lt;/p&gt;
</description><pubDate>Wed, 18 May 2016 00:00:00 GMT</pubDate><category>personal</category></item></channel></rss>