<?xml version="1.0" encoding="utf-8"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
     <title>BigBinary Blog</title>
     <link href="https://www.bigbinary.com/feed.xml" rel="self"/>
     <link href="https://www.bigbinary.com/"/>
     <updated>2026-08-08T10:16:22+00:00</updated>
     <id>https://www.bigbinary.com/</id>
     <entry>
       <title><![CDATA[Benchmarking Crunchy Data for latency]]></title>
       <author><name>Vishnu M</name></author>
      <link href="https://www.bigbinary.com/blog/crunchy-bridge-vs-digital-ocean"/>
      <updated>2024-10-15T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/crunchy-bridge-vs-digital-ocean</id>
      <content type="html"><![CDATA[<p>In Rails World 2024, DHH unveiled <a href="https://kamal-deploy.org/">Kamal 2</a> in his<a href="https://www.youtube.com/watch?v=-cEn_83zRFw">opening keynote</a>. Now, folks wantto give Kamal a try, but some people are worried about the data. They want totake one step at a time, and they feel more comfortable if their database ismanaged by someone else.</p><p>That's where <a href="https://www.crunchydata.com/">Crunchy Data</a> comes in. They providemanaged Postgres service. Checkout this<a href="https://x.com/dhh/status/1840901376182009900">tweet</a> from DHH about CrunchyData.</p><p>In our internal discussion, one of the BigBinary engineers brought up the issueof &quot;latency&quot;. Since the PostgreSQL server will not be in the same data center,what would be the latency? How much impact will it have on performance?</p><p>We didn't know the answer so we thought we would do some benchmarking.</p><h2>Benchmarking</h2><p>To do the comparison we needed another hosting provider where we can runPostgreSQL on the same data center. We chose to work with Digital Ocean.</p><p>To compare the two services, we wrote a benchmark script in Ruby similar to theone <a href="https://x.com/benbjohnson">Ben Johnson</a> wrote in<a href="https://github.com/benbjohnson/production-sqlite-go/blob/main/postgres-tests/postgres_test.go">Go</a>for his <a href="https://youtu.be/XcAYkriuQ1o?si=vz-sYjevztb_bnwL">GopherCon talk</a>in 2021.</p><p>In this benchmark, we're using Ruby's Benchmark module to measure theperformance of a series of database operations. Here's what the code does:</p><ol><li><p>It establishes a connection to the database only once, at the beginning ofthe script. This is done outside the benchmarked operations becauseestablishing connections can be a slow operation because of TLS negotiation,and we don't want to account for that time in our measurements.</p></li><li><p>It then performs the following operations 10,000 times, measuring each one:</p><ul><li>Drops a table named 't' if it exists.</li><li>Creates a new table 't' with two columns: 'id' (an auto-incrementingprimary key) and 'name' (a text field).</li><li>Inserts a single row into the table with the name 'jane'.</li><li>Selects the 'name' from the table where the 'id' is 1 (which should be'jane').</li></ul></li><li><p>After all 10,000 iterations, it calculates and prints the average time foreach operation in microseconds.</p></li></ol><pre><code class="language-ruby">require &quot;pg&quot;require &quot;benchmark&quot;class PostgresBenchmark  def initialize(connection_string)    @conn = PG.connect(connection_string)  end  def run(iterations = 10_000)    total_times = Hash.new { |h, k| h[k] = 0 }    iterations.times do |i|      puts &quot;Running iteration #{i + 1}&quot; if (i + 1) % 1000 == 0      times = benchmark_operations      times.each { |key, time| total_times[key] += time }    end    average_times = total_times.transform_values { |time| time / iterations }    print_results(average_times, iterations)  ensure    @conn.close if @conn  end  private  def benchmark_operations    times = {}    times[:drop] = Benchmark.measure { @conn.exec(&quot;DROP TABLE IF EXISTS t&quot;) }.real    times[:create] = Benchmark.measure { @conn.exec(&quot;CREATE TABLE t (id SERIAL PRIMARY KEY, name TEXT)&quot;) }.real    times[:insert] = Benchmark.measure { @conn.exec(&quot;INSERT INTO t (name) VALUES ('jane')&quot;) }.real    times[:select] = Benchmark.measure do      result = @conn.exec(&quot;SELECT name FROM t WHERE id = 1&quot;)      raise &quot;Unexpected result&quot; unless result[0][&quot;name&quot;] == &quot;jane&quot;    end.real    times  end  def print_results(times, iterations)    total_time = times.values.sum    puts &quot;\nAVERAGE ELAPSED TIME (over #{iterations} iterations)&quot;    puts &quot;drop    #{(times[:drop] * 1_000_000).round(2)} microseconds&quot;    puts &quot;create  #{(times[:create] * 1_000_000).round(2)} microseconds&quot;    puts &quot;insert  #{(times[:insert] * 1_000_000).round(2)} microseconds&quot;    puts &quot;select  #{(times[:select] * 1_000_000).round(2)} microseconds&quot;    puts &quot;TOTAL   #{(total_time * 1_000_000).round(2)} microseconds&quot;  endendif __FILE__ == $0  connection_string = &quot;&lt;DB_CONNECTION_STRING&gt;&quot;  benchmark = PostgresBenchmark.new(connection_string)  benchmark.run(10_000)end</code></pre><h2>Database Specifications and Setup</h2><h3>Digital Ocean</h3><ul><li>Region: NYC3 data center</li><li>Specs: 2 vCPU, 4GB Memory</li><li>Price: $60 per month</li></ul><h3>Crunchy Data</h3><ul><li>Provider: AWS</li><li>Region: us-east-1</li><li>Specs: 2 vCPU, 4GB Memory (hobby-4 plan)</li><li>Price: $70 per month</li></ul><p>We provisioned a Digital Ocean droplet in the NYC3 data center and invoked thebenchmark script from the machine. The Digital Ocean database was also in thesame NYC3 data center. For Crunchy Data, the availability zone selected was<code>us-east-1</code> as it was the closest to NYC3.</p><h2>Benchmarking results</h2><h3>Digital Ocean</h3><pre><code class="language-js">AVERAGE ELAPSED TIME (over 10000 iterations)drop    3448.07 microsecondscreate  5048.39 microsecondsinsert  891.81 microsecondsselect  584.17 microsecondsTOTAL   9972.44 microseconds</code></pre><h3>Crunchy Data</h3><pre><code class="language-js">AVERAGE ELAPSED TIME (over 10000 iterations)drop    10097.89 microsecondscreate  16818.63 microsecondsinsert  8416.35 microsecondsselect  7211.42 microsecondsTOTAL   42544.29 microseconds</code></pre><h2>Benchmarking analysis</h2><p>The results of this benchmark do not come as a surprise. Digital Ocean isperforming significantly better than Crunchy Data. This performance differencecan be primarily attributed to network latency.</p><p><em>Network latency</em> refers to the round-trip time (RTT) it takes for the data totravel from its source to its destination and back again across a network. Inthe context of database operations, it's the time taken for a query to be sentfrom the client to the database server and for the response to return to theclient.</p><p>In our benchmarking, the Digital Ocean database and the client machine invokingthe script were both located in the same data center (NYC3), resulting inminimal network latency. On the other hand, the Crunchy Data database was hostedin AWS <code>us-east-1</code>, and it had to communicate across a greater physicaldistance, adding to latency.</p><p>To get a more accurate value for the network latency, we can compare the averagetime taken to run the <code>SELECT</code> operation. The <code>SELECT</code> operation in the scriptis a point query. <em>A point query refers to type of query that retrieves one orseveral rows based on a unique key</em>.</p><p>In our script, it retrieves a single <code>name</code> value from the table <code>t</code> where the<code>id</code> is <code>1</code>(which is the primary key), and is very fast to execute. Thus, thetime taken to execute the <code>SELECT</code> operation can give us an approximate valuefor the network latency.</p><pre><code>db_time = network_latency + query_execution_time</code></pre><p>For point queries, the <code>query_execution_time</code> is almost zero so all the timetaken is pretty much &quot;network latency&quot;.</p><pre><code>db_time  network_latency</code></pre><p>If we look at the benchmarking result, then we can see that for &quot;select&quot;operation time taken by Digital Ocean is &quot;584 microseconds&quot; and for Crunchy Datait is &quot;7211 microseconds&quot;.</p><p>The difference in network latency is <em>6627 microseconds</em>. That is 6.6milliseconds.</p><p>This value over multiple queries can add up and can have a significant impact onthe overall response time of your application. To put this into perspective, fora web application that makes 10 sequential database queries to render a page,this could add up to about <em>66 milliseconds</em> to the page load time. Now, thiscould be an acceptable limit if your page loads in 3/4 seconds. However,if you are trying to load your page in 200 millisecond,s then you need to watchout.</p><p>Ensuring that the database is always up and it's properly backed up is a non-trivialproblem. Latency notwithstanding, Crunchy Data takes care of running the database.This gives us peace of mind and allows us to exit the cloud one step at a time.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Improving NeetoRecord reach with Twitter player cards]]></title>
       <author><name>Bonnie Simon</name></author>
      <link href="https://www.bigbinary.com/blog/adding-twitter-player-cards-to-neetorecord"/>
      <updated>2024-08-16T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/adding-twitter-player-cards-to-neetorecord</id>
      <content type="html"><![CDATA[<p>At Neeto, we're building multiple products, and we love sharing our progress andupdates on Twitter. We often accompany our tweets with NeetoRecord recordings togive you an even closer look at our work. However, we noticed that users had toleave Twitter to view our videos, creating unnecessary friction in theirexperience.</p><h4>The Problem</h4><p>Initially, when we shared a NeetoRecord video on Twitter, this is how the tweetwould appear.</p><p><img src="/blog/images/images_used_in_blog/2024/adding-twitter-player-cards-to-neetorecord/no-embed.png" alt="Link with no embed"></p><p>As we can see, the links are just plain text, lacking visual appeal and context.Users had to click away from Twitter and open a new tab to view the recording.This extra step not only disrupted the user's Twitter experience but also likelyreduced the number of people who actually watched our videos.</p><h4>The Solution: Twitter Cards</h4><p>To address this issue, we turned to Twitter Cards. Similar to Facebook's OpenGraph protocol, Twitter Cards allows us to showcase interactive media directlywithin tweets. While both protocols serve similar purposes, Twitter cards aredesigned to directly work in Twitter, so its crawler will look for these tagsand only look for OG tags as a fallback.</p><h4>What are Twitter Cards?</h4><p>Twitter Cards are a set of meta tags that enable us to embed interactive contenton a tweet. In our case, we want to provide users an embedded video player whichthey can use to view the NeetoRecord recording without leaving Twitter. It's notjust about aesthetics. According to Twitter's own data, &quot;tweets with cards have43% higher engagement rates than regular tweets with links.&quot; This statisticunderscores the impact of providing a seamless, visually appealing experience.</p><h4>Implementing Twitter Cards for NeetoRecord</h4><p>There are multiple types of cards that we can leverage to enhance our contentsuch as summary cards, player cards &amp; app cards. However, in this post, we aregoing to be discussing about Player cards, which is implemented in NeetoRecord.This card type allows us to embed an interactive video player directly in thetweet. Here's how it looks:</p><p><img src="/blog/images/images_used_in_blog/2024/adding-twitter-player-cards-to-neetorecord/player-embed.png" alt="Embed player"></p><p>Clicking on this link preview will open our embedded content, in this case, aplayer to view the recording directly within Twitter.</p><p><img src="/blog/images/images_used_in_blog/2024/adding-twitter-player-cards-to-neetorecord/player-embed-expanded.png" alt="Expanded player"></p><p>As we can see, users can now watch our NeetoRecord videos without leavingTwitter. This seamless experience has several benefits:</p><ul><li>Higher Engagement: With the video right there in the tweet, more users arelikely to watch it.</li><li>Reduced Friction: No more clicking away or opening new tabs, keeping usersengaged with our content and the Twitter conversation.</li><li>Better Branding: The Player Card includes our logo and video title,reinforcing our brand with every view.</li></ul><h4>Technical Implementation</h4><p>Implementing Twitter Cards is straightforward. We added meta tags to the<code>&lt;head&gt;</code> section of our web page. For our Player Card, we use tags as shownbelow.</p><pre><code class="language-html">&lt;meta property=&quot;twitter:card&quot; content=&quot;player&quot; /&gt;&lt;meta  property=&quot;twitter:url&quot;  content=&quot;https://oli.neetorecord.com/watch/864de7fb-2efb-4f2f-a60b-08dca64e4c3&quot;/&gt;&lt;meta  property=&quot;twitter:title&quot;  content=&quot;Introducing the new video player for NeetoRecord&quot;/&gt;&lt;meta property=&quot;twitter:site&quot; content=&quot;@NeetoRecord&quot; /&gt;&lt;meta property=&quot;twitter:image&quot; content=&quot;https://cdn.neeto.com/hycoe7&quot; /&gt;&lt;meta  property=&quot;twitter:player&quot;  content=&quot;https://oli.neetorecord.com/embeds/864de7fb-2efb-4f2f-a60b-08dca64e4c3&quot;/&gt;&lt;meta property=&quot;twitter:player:width&quot; content=&quot;1280&quot; /&gt;&lt;meta property=&quot;twitter:player:height&quot; content=&quot;720&quot; /&gt;</code></pre><p>These tags tell Twitter's crawler what to display in the card.</p><p>Let's break down the purpose and importance of each meta tag:</p><pre><code class="language-html">&lt;meta property=&quot;twitter:card&quot; content=&quot;player&quot; /&gt;</code></pre><p>This tag specifies the card type. Setting it to &quot;player&quot; tells Twitter that thisis a Player Card, which is designed for video or audio content.</p><pre><code class="language-html">&lt;meta  property=&quot;twitter:url&quot;  content=&quot;https://oli.neetorecord.com/watch/864de7fb-2efb-4f2f-a60b-08dca64e4c3&quot;/&gt;</code></pre><p>This tag provides the URL of the web page that the card is describing. It shouldbe the page where users can view the full content.</p><pre><code class="language-html">&lt;meta  property=&quot;twitter:title&quot;  content=&quot;Introducing the new video player for NeetoRecord&quot;/&gt;</code></pre><p>This tag sets the title of the card, which appears as the main headline.</p><pre><code class="language-html">&lt;meta property=&quot;twitter:site&quot; content=&quot;@NeetoRecord&quot; /&gt;</code></pre><p>This tag specifies the Twitter @username the card should be attributed to.</p><pre><code class="language-html">&lt;meta property=&quot;twitter:image&quot; content=&quot;https://cdn.neeto.com/hycoe7&quot; /&gt;</code></pre><p>This tag provides the image to be displayed in place of the player on platformsthat dont support iFrames or inline players.</p><pre><code class="language-html">&lt;meta  property=&quot;twitter:player&quot;  content=&quot;https://oli.neetorecord.com/embeds/864de7fb-2efb-4f2f-a60b-08dca64e4c3&quot;/&gt;</code></pre><p>This crucial tag specifies the URL of the video player. This should be an HTTPSURL to an iframe player that can play the content.</p><pre><code class="language-html">&lt;meta property=&quot;twitter:player:width&quot; content=&quot;1280&quot; /&gt;&lt;meta property=&quot;twitter:player:height&quot; content=&quot;720&quot; /&gt;</code></pre><p>These two tags define the width and height of the video player iframe, inpixels. They help ensure the video displays correctly in the Twitter feed.</p><h4>Conclusion</h4><p>Twitter Cards have improved how NeetoRecord videos are shared on Twitter. Theembedded media experience reduces friction and increases engagement, whileenhancing our brand visibility.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Honeybadger frontend integration in Neeto apps]]></title>
       <author><name>Calvin Chiramal</name></author>
      <link href="https://www.bigbinary.com/blog/honeybadger-frontend-integration-in-neeto-apps"/>
      <updated>2023-12-12T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/honeybadger-frontend-integration-in-neeto-apps</id>
      <content type="html"><![CDATA[<p>At Neeto, we integrated Honeybadger to track all errors in our applications atruntime. We also integrated Honeybadger with GitHub to automatically raiseissues in the respective repositories when errors are caught. This blog is anin-depth guide on how we integrated Honeybadger on the frontend part of our webapps.</p><p>For our apps deployed on Heroku, we used the following command to get the latestcommit hash as an environment variable in the Heroku server on each build:</p><pre><code class="language-bash">heroku labs:enable runtime-dyno-metadata -a your-app-name</code></pre><p>With NeetoDeploy, we <a href="https://youtu.be/r4g-a6k5SYY?t=176">didn't need this step</a>since git is available in the console and we used git commands to set the latestcommit hash as the Honeybadger revision. Honeybadger uses the<a href="https://docs.honeybadger.io/lib/javascript/guides/using-source-maps/#versioning-your-project">revision</a>to associate maps with the bundle when the bundle name doesn't change.</p><p>Steps to integrate Honeybadger:</p><ol><li><p>Create a new project in the Honeybadger dashboard. We've made a<a href="https://youtu.be/h5svJ15Vg5Q">video</a> on setting it up following the<a href="https://docs.honeybadger.io/lib/javascript/integration/react/">React integration guide</a>.</p></li><li><p>Set the environment variable <code>HONEYBADGER_JS_API_KEY</code> with the Honeybadgerproject's API key. The API key can be copied from <code>Settings =&gt; API Keys</code>.</p></li></ol><p>In depth guides for Honeybadger integration &amp; sourcemap upload:</p><ul><li><a href="https://youtu.be/h5svJ15Vg5Q">What is honeybadger</a></li><li><a href="https://youtu.be/vtIk6g-NekA">Honeybadger integration basics</a></li><li><a href="https://youtu.be/yRrKBnN00Yc">Honeybadger integration in the Neeto ecosystem</a></li><li><a href="https://youtu.be/qZD9pus_9ro">Sourcemaps explained</a></li><li><a href="https://youtu.be/r4g-a6k5SYY">Uploading sourcemap to honeybadger</a></li><li><a href="https://youtu.be/tmnxx7HZ5bw">Sourcemaps in action within Honeybadger errors</a></li><li><a href="https://youtu.be/AZZWDMRuuY8">Handling CDN based application bundles</a></li></ul><p>We've created a YouTube<a href="https://www.youtube.com/playlist?list=PLRpdquznQk7E6UPRx7yyAAJrPYwxXqPS4">playlist</a>of the above videos for easy access.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Deep Dive into Redis Data Types]]></title>
       <author><name>Sreeram Venkitesh</name></author>
      <link href="https://www.bigbinary.com/blog/redis-data-types-deep-dive"/>
      <updated>2023-11-14T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/redis-data-types-deep-dive</id>
      <content type="html"><![CDATA[<p>Last week, while migrating <a href="https://www.neeto.com/neetogit">NeetoGit's</a>production deployment to <a href="https://www.neeto.com/neetodeploy">NeetoDeploy</a>, wefaced a challenge. The Redis 7 add-on only had a TLS URL in Heroku. We were notable to get the dump in a straightforward manner using redis-cli, since wedidn't have access to the certificates for making a TLS connection. We were ableto connect to the add-on, so we decided to write a script to manually copy allthe keys and values over to the Redis add-on in NeetoDeploy.</p><p>While writing the program, we realized that we'd need a switch-case that checkedwhat data type each key was. We had to search for the get and set methods foreach data type manually while writing the program. Late,r we used the redis-rbgem and was able to get the dump with <code>OpenSSL::SSL::VERIFY_NONE</code>, but we hadstill faced the issue of having to look up what the command was for differentdata types like zset and hash.</p><p>I thought this could be a good opportunity to write a blog post summarizing allthe data types and the commands associated with them. The Redis documentationhas a page about the different data types, but all the commands are notavailable at a single place.</p><h2>Redis key-value database</h2><p>When we say that Redis is a key-value database, there is more to it than whatmeets the eye. Redis keys can store values of several different data types.Redis has a set of commands for each of these different data types to dooperations with them. In this post, well go over the different data types, whatthey are and how we can work with them.</p><h3>What are the different data types?</h3><p>Redis has more than a couple of data types, which can be used to store differentdata based on your needs. These include the following:</p><ul><li><code>String</code> - The basic data type we are all familiar with.</li><li><code>List</code> - An array of strings.</li><li><code>Hash</code> - A collection of key-value pairs, similar to a Ruby <code>Hash</code>.</li><li><code>Set</code> - A collection of unique strings.</li><li><code>Sorted Set</code> - A collection of unique strings maintaining by each stringsscore.</li><li><code>HyperLogLog</code> - Probabilistic estimates of cardinality of large sets.</li><li><code>Stream</code> - An append only log.</li><li><code>Geospatial Index</code> - Data structure for storing geographic coordinates.</li></ul><h3>Checking data type of your keys</h3><p>You can use the <code>TYPE</code> command to check what data type your key is. Once youknow what type your key is, you can use the commands associated with your key'sdata type to interact with it.</p><pre><code>127.0.0.1:6379&gt; TYPE schedulezset</code></pre><h3>Cheatsheet for Redis commands based on data type</h3><p>Each Redis data type has its own set of commands for doing operations with thekey and its value. Here's a quick overview of the basic data types you wouldencounter and some of the basic commands to set, retrieve and delete data fromthem.</p><p><img src="/blog/images/images_used_in_blog/2023/redis-data-types-deep-dive/redis-commands-based-on-data-types.png" alt="A list of Redis commands for doing operations with each data type."></p><p>Read more about the different data types in Redis and all the different commandsthat are available in their<a href="https://redis.io/docs/data-types/">official documentation</a>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Profiling your zsh setup with zprof]]></title>
       <author><name>Sreeram Venkitesh</name></author>
      <link href="https://www.bigbinary.com/blog/zsh-profiling"/>
      <updated>2023-10-12T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/zsh-profiling</id>
      <content type="html"><![CDATA[<p>While using frameworks like <a href="https://ohmyz.sh/">oh-my-zsh</a> to upgrade yourshell, it is pretty easy to get carried away with all the available plugins.This can eventually take a toll on your shells performance. One significant wayit can affect your workflow is by slowing everything down. The more items youadd to your <code>.zshrc</code> file, the more time your shell will need to start up.Profiling your shell is a good start to figuring out what is slowing it down.</p><h3>zprof</h3><p><a href="https://zsh.sourceforge.io/Doc/Release/Zsh-Modules.html#The-zsh_002fzprof-Module">zprof</a>is a utility that comes packaged with zsh, which you can use to profile your zshscript.</p><p>Add the following to the top of your <code>.zshrc</code> file to load zprof.</p><pre><code class="language-bash">zmodload zsh/zprof</code></pre><p>At the bottom of your <code>.zshrc</code>, add the following.</p><pre><code class="language-bash">zprof</code></pre><p>This would profile your zsh script and print a summary of all the commands runduring your shell startup and the time it takes to execute them. Run <code>exec zsh</code>to apply the changes and restart your shell. Your shell will print somethinglike this:</p><p><img src="/blog/images/images_used_in_blog/2023/zsh-profiling/zprof-output.webp" alt="Output of the zprof command"></p><p>With this you can see which commands are taking the most time to load. Enablingprofiling has helped pinpoint the issue and now you can look into fixing it. Inthe above example, you can see that nvm is taking up a considerable amount oftime.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Domain redirects with different crawl rules via Cloudflare]]></title>
       <author><name>Ghouse Mohamed</name></author>
      <link href="https://www.bigbinary.com/blog/domain-redirection-using-cloudflare"/>
      <updated>2023-08-31T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/domain-redirection-using-cloudflare</id>
      <content type="html"><![CDATA[<p><a href="https://neeto.com">neeto</a> is a collection of different software. Each neetoproduct gets its own page. For example, NeetoCal gets the URLhttps://www.neeto.com/neetocal.</p><p>When it comes to actually using these products, you will be redirected to<code>https://subdomain.neetocal.com</code>. Here, &quot;subdomain&quot; would be the subdomainallocated to you when you signed up for Neeto.</p><p>We planned to add &quot;Google sign in&quot; feature to make it easier for folks toboth signup and to log in. During testing, it all worked fine. However, when weasked &quot;Google&quot; to approve the app &quot;NeetoCal&quot; for &quot;Google sign in&quot; Googledemanded that our users should be able to see the &quot;Privacy Policy&quot; and &quot;Terms ofconditions&quot; on the website. In order to make Google happy, we added a redirectionfrom &quot;neetocal.com&quot; to &quot;neeto.com/neetocal&quot;.</p><p>However, Google was not happy with it. The users are logging into<code>https://subdomain.neetocal.com</code> so the &quot;privacy policy&quot; and &quot;terms of service&quot;should be visible on the domain &quot;neetocal.com&quot; itself.</p><p>We are using Cloudflare as our DNS provider. Using the tools provided to us byCloudflare we decided to show the content of &quot;neeto.com/neetocal&quot; on&quot;neetocal.com&quot; without redirecting the user.</p><p>Note that in this case, if you type &quot;neetocal.com&quot;, then you will see the URLchange to &quot;neetocal.com/neetocal&quot; instantly. That's because the URL of the mainmarketing site is &quot;neeto.com/neetocal&quot;.</p><p>Cloudflare provides<a href="https://developers.Cloudflare.com/support/page-rules/understanding-and-configuring-Cloudflare-page-rules-page-rules-tutorial/">Page rules</a>which we will be using to achieve our goals. Below is a video of how it was done.</p><p>&lt;iframewidth=&quot;100%&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/RodZIBxYBHc&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote><h2>SEO duplicate content issue</h2><p>The Google search engine doesn't like it when we show exactly the same contenton two different domains. Google thinks that the site is trying to cheat Googleand Google will punish both sites.</p><p>We want Google to index our main marketing site https://neeto.com/neetocal andwe want Google to ignore &quot;neetocal.com&quot;. One way to tell Google not index thesite is by adding a <code>noindex</code><a href="https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#noindex">meta tag</a>.</p><pre><code>&lt;meta name=&quot;robots&quot; content=&quot;noindex&quot;&gt;</code></pre><p>In the above example, we are asking all bots not to index the page containingthe above meta tag.</p><p>We planned to inject this meta tag when a page is rendered for the URL&quot;neetocal.com&quot; and we will not inject this tag when the page is rendered for the URL&quot;neeto.com&quot;.</p><p>Upon more research, we found that search engines also look at the responseheaders. Given below is the sequence that search engines follow for indexing theweb pages.</p><ul><li>Crawler gets the raw page source as a response to the HTTP request.</li><li>Crawler checks if <code>x-robots-tag: noindex, nofollow</code> header is present in theresponse.</li><li>Crawler checks the meta tags to determine if the page needs to be indexed ornot.</li></ul><p>If a page has <code>x-robot-tag: noidex, nofollow</code> then the crawler will not indexthe page.</p><p>Based on this information, we decided to use<a href="https://developers.Cloudflare.com/rules/transform/response-header-modification/">Response Header Modification Rules</a>feature of Cloudflare. Below is a video of how it was done.</p><p>&lt;iframewidth=&quot;100%&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/0AFeM2yyg_A&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote>]]></content>
    </entry><entry>
       <title><![CDATA[Debugging high GitHub action usage]]></title>
       <author><name>Unnikrishnan KP</name></author>
      <link href="https://www.bigbinary.com/blog/high-github-action-usage"/>
      <updated>2023-08-08T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/high-github-action-usage</id>
      <content type="html"><![CDATA[<p>During the development of <a href="https://neeto.com">neeto</a> we noticed arbitrarily veryhigh GitHub action usage. I investigated the matter and made this video to showto my team members how I went about debugging this issue. The video is beingpresented &quot;as it was recorded&quot;.</p><p>&lt;iframewidth=&quot;560&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/eS4BAhk7DAo&quot;title=&quot;Debugging high GitHub action usage&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote>]]></content>
    </entry><entry>
       <title><![CDATA[Upgrading to TLS 1.2 using Cloudflare]]></title>
       <author><name>Ghouse Mohamed</name></author>
      <link href="https://www.bigbinary.com/blog/upgrading-tls-using-cloudflare"/>
      <updated>2023-08-03T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/upgrading-tls-using-cloudflare</id>
      <content type="html"><![CDATA[<p><a href="https://neeto.com/neetocal">NeetoCal</a> is one of the products built under<a href="https://neeto.com">neeto</a>. NeetoCal makes it easier to manage meetings. Wewanted to allow users to use Zoom as one of the ways to have online meetings. Wesubmitted the NeetoCal app for approval to the Zoom team. The Zoom security teamnotified us that they could not approve the app, because the app was supportingTLS 1.0 and TLS 1.1.</p><p><img src="/blog/images/images_used_in_blog/2023/upgrading-tls-using-cloudflare/zoom-tls-issue.webp" alt="zoom tls issues"></p><p>We checked with SSLlabs and it said the same thing: the servers support TLS 1.0and TLS 1.1.<img src="/blog/images/images_used_in_blog/2023/upgrading-tls-using-cloudflare/older-tls-support.png" alt="support for older TLS"></p><p>TLS 1.0 was published in 1999, and TLS 1.1 was published in 2006. Microsoft andother companies don't support these two versions of TLS. Even Heroku<a href="https://help.heroku.com/G0YVUNPG/how-do-i-disable-support-for-tls-1-0-or-1-1-on-a-heroku-app">doesn't support</a>it.</p><p>All our Neeto applications are hosted on Heroku. If Heroku doesn't support TLS1.0 and TLS 1.1, how come the server supports these older versions of TLS?</p><h2>Solving the TLS issue using Cloudflare</h2><p>We use <a href="https://www.cloudflare.com/">Cloudflare</a> as our DNS server for all Neetoproducts. Cloudflare allows us to proxy the request. It means that when the userhits neetocal.com, their request is not going to Heroku. Cloudflare willintercept the request, and then Cloudflare will make a request to the Herokuserver on behalf of the user. When Cloudflare makes this request to Heroku willuse its own SSL certificate.</p><p>Cloudflare allows us to have control over the &quot;Minimum TLS version&quot; to support.We configured Cloudflare to not support TLS 1.0 and TLS 1.1.</p><p>The following video goes into step-by-step detail on how we configured this inCloudflare.</p><p>&lt;iframewidth=&quot;560&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/sED8_Qwmi2w&quot;title=&quot;Upgrading to TLS 1.2 using Cloudflare&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote><p><a href="https://www.cdn77.com/tls-test">CDN77</a> is the service we used in the video tocheck the TLS version.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Deleting all staging apps and building automatic backups]]></title>
       <author><name>Subin Siby</name></author>
      <link href="https://www.bigbinary.com/blog/routine-db-exports-neetodeploy"/>
      <updated>2023-07-14T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/routine-db-exports-neetodeploy</id>
      <content type="html"><![CDATA[<p>We are building <a href="https://neeto.com/neetodeploy">NeetoDeploy</a>, an alternative forHeroku makes it easy to deploy and manage applications on the cloud. It is built with amix of Kubernetes and Rails.</p><p>We switched our pull-request<a href="https://devcenter.heroku.com/articles/github-integration-review-apps">review apps from Heroku</a>to NeetoDeploy a couple of months ago, and it has been doing well. As the nextstep of the process, we are building features to target staging and production.We had an essential staging set up in March, and by the end of the month, we hadmigrated staging deployments of all Neeto from Heroku to NeetoDeploy. Everythingwas working fine for a week until I made a grave mistake.</p><h2>What happened</h2><p>Whenever a PR is opened or a new commit is pushed, NeetoDeploy receives awebhook call from GitHub. Review/staging apps are created/updated on NeetoDeployin response to these webhook calls.</p><p>On 2023 April 5, a misconfiguration caused NeetoDeploy's webhook handler tomalfunction for an hour. As a result, some review apps were not deleted evenafter the corresponding PRs were closed or merged. The solution wascross-checking review apps with the open PRs and deleting the unwanted apps.Using <code>rails console</code>, this could be done live on the server.</p><p>Here is what that solution looked like:</p><pre><code class="language-ruby">GithubRepository.find_each do |github_repository|  access_token = github_repository.github_integration.access_token  github_client = Octokit::Client.new(access_token:)  open_pr_numbers = github_client    .pull_requests(github_repository.name, state: :open)    .pluck(:number)  github_repository.project.apps.find_each do |app|    next if open_pr_numbers.include?(app.pr_number)    Apps::DestroyService.new(app).process!  endend</code></pre><p>But there is a terrible mistake in the above code. See if you can spot that.</p><p>I'm going to wait...</p><p>A bit more waiting... Enough waiting; here's the mistake:</p><p>There is no filter in the apps that were picked to be destroyed. This snippetwas written at a time when we only had review apps. So<code>github_repository.project.apps</code> was expected to return review apps. But we nowalso had staging apps in the database. And those staging apps weren't filteredout here. After running the snippet and noticing it took longer than expected, Irealized the mistake and instantly pressed<code>CTRL + C</code>. Of course, it was takingtime since it was deleting all the staging app databases and dynos .</p><p>In the end, out of 33 staging apps, only five remained. And thus started, theprocedure to restore all of them.</p><h2>The recovery</h2><p>NeetoDeploy already had the feature to do manual<a href="https://help.neetodeploy.com/articles/database-exports">DB exports</a> but thiswasn't being done routinely. We were only hosting review apps (whose data neednot be persisted reliably), and staging had only started just a week before.</p><p>We had database backups from a week before (when we ultimately migrated stagingapps off Heroku), and one by one, our small team of 4 brought back all the appsin 2 days. The next step is to try not to let this happen again; if it were tohappen, we have a contingency plan. We thought of two types of contingencyplans:</p><ul><li>Automatic scheduled backups</li><li>Disk snapshots of the DB</li></ul><h2>Automatic scheduled backups</h2><p>The idea is that the database would be exported at a particular time every day.Backups older than a month would be deleted automatically to save space.</p><p>We implemented this in a week. Every day at 12 AM UTC, all staging+productiondatabases would be exported and uploaded to an S3 bucket.</p><p>While this feature was being implemented, I used the Rails console to manuallyexport all the apps. The exported file URLs of each DB were manuallycopied to a text file. <a href="https://aria2.github.io/">aria2c</a> was then used todownload them in parallel to a local folder:</p><pre><code class="language-bash">aria2c -c --input-file export_urls.txt</code></pre><p>aria2c is a smart downloader. It will resume interrupted downloads, wouldntduplicate downloads, and do everything in parallel.</p><h2>Disk snapshots of the DB</h2><p>The other contingency method is to do periodic snapshots of the volume holdingthe DB. We are working on this.</p><p>You can refer to this<a href="https://about.gitlab.com/blog/2017/02/10/postmortem-of-database-outage-of-january-31/#broken-recovery-procedures">blog post of GitLab</a>to know their recovery procedures when they faced a significant data lossin 2017.</p><h2>Lessons</h2><p>The core lesson here is to call destructive methods very carefully. Instead ofcalling the <code>DestroyService</code> instantly, there could have been an intermediatehuman check:</p><pre><code class="language-ruby">apps = []GithubRepository.find_each do |github_repository|  access_token = github_repository.github_integration.access_token  github_client = Octokit::Client.new(access_token:)  open_pr_numbers = github_client    .pull_requests(github_repository.name, state: :open)    .pluck(:number)  github_repository.project.apps.review.find_each do |app|    next if open_pr_numbers.include?(app.pr_number)    apps.append(app)  endend</code></pre><p>This would populate the list of apps to delete in <code>apps</code> variable, it can bedisplayed, verified and then we can destroy them individually:</p><pre><code class="language-ruby">apps.map do |app|  Apps::DestroyService.new(app).process!end</code></pre><p>The other takeaway here is to have proper recovery mechanisms in place.Human/system errors are possible; we should be prepared when it happens.</p><p><a href="https://neeto.com/neetodeploy">NeetoDeploy</a> is still not production-ready.However, if you want to give NeetoDeploy a try, then tweet to us at<a href="https://twitter.com/neetodeploy">@neetoDeploy</a> or send us an email at<code>invite@neeto.com</code>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[How SPF protects domain reputation and email delivery]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/how-spf-protects-domain-reputation"/>
      <updated>2023-04-26T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-spf-protects-domain-reputation</id>
      <content type="html"><![CDATA[<p>Email is a wonderful thing. Anyone can send an email to anyone, and it all worksbeautifully. In the early stages of the Internet, we didn't have to worry aboutsecurity and scammers. As emails started to play a more vital role in our lifescammers started to con people to steal their money.</p><p>Not only can anyone send an email to anyone, but anyone can pretend to be anyone. Ican send an email to you and the email will come to you as if it were sent by&quot;Elon Musk&quot; and the from address could be &quot;elon@musk.com&quot;.</p><p>I can send an email to you pretending that the email is from your bank and youneed to change your password. In one case, a hacker sent an email to a company'sfinance team telling them that the company's bank account number has changed.The hacker walked away with millions of dollars.</p><p>Hackers can also do damage to one's domain reputation.</p><p>Let's assume that there is a business called &quot;PrixPapers&quot; and they havethousands of customers all over the country. Malicious folks can start sendingemails to people pretending to be &quot;PrixPapers&quot;. When people receive an emaillike this they might buy the item. The item could be &quot;fake goods&quot;. Ideally, noone should be able to send any email pretending to be someone else.</p><p>Since these malicious folks don't have the actual list of the customers ofPrixPapers will in general spam people. Some people receiving this email willmark these emails as &quot;spam&quot;. As more and more people mark these emails as &quot;spam&quot;the domain reputation of &quot;prixpapers.com&quot; will go down. It means that when&quot;prixpapers.com&quot; will send out a legitimate newsletter to its customers some ofthese emails will be marked as &quot;spam&quot; because of the nefarious work done by theprevious scammer.</p><p>The challenge presented to the Internet authorities is that they want to keepemail simple but safe at the same time. They came up with certain policies tomake emails safe and secure. In this blog, we will see how SPF helps in keepingemail secure. However, first, we need to know what is &quot;Return-path&quot;. We'll latersee how it is used in blocking fake emails.</p><h3>Return-path</h3><p>Let's say that we are dealing with a company called<a href="https://www.neeto.com/neetocal">NeetoCal</a>. They have the domain <em>neetocal.com</em>.</p><p>Let's say that <em>brian@neetocal.com</em> sent an email to <em>peter@gmail.com</em>. Let'spretend that for some reason Peter's mailbox is full and the email sent by Brianbounces. In that case, Brain will get an email that the message bounced. We allhave seen these kinds of messages.</p><p>Now let's imagine that it's end of the year sale time and NeetoCal decided tosend an email campaign to all its customers giving them a 10% discount. Thisemail will go out to their 5000 customers. Some of these emails are bound tobounce. If all the bounced emails come to Brain then Brain's inbox would befilled with such bounced emails.</p><p>To solve this problem email protocol allows us to set a hidden field called<em>Return-path</em>. This value can be set in the email header. <em>Return-path</em> set inthe email header indicates how to process bounced emails. Anytime an emailserver detects that an email can't be delivered for reasons like &quot;mailbox isfull&quot; or &quot;the email doesn't exist&quot; then that email server can send an email tothe email address mentioned in <em>Return-path</em>. The business owner can look at<em>Return-path</em> emails to analyze how many emails are bouncing and why.</p><p>If you are sending an email using &quot;Gmail&quot;, &quot;outlook&quot;, &quot;yahoo&quot; etc then the<em>Return-path</em> is set as your email so that you get to know if there is a bouncedemail. Email service providers allow us to customize <em>Return-path</em> in case oneis running a big marketing campaign. For example here is a<a href="https://sendgrid.com/blog/what-is-return-path">document</a> from SendGriddescribing how to set <em>Return-path</em>.</p><p>Why are we talking about how bounced emails are processed when we are dealingwith the subject of &quot;email deliverability&quot;. That's because <em>Return-path</em> plays adual role as we will see next.</p><h2>What is SPF record</h2><p>Before we get into the SPF record, let's take a simple real-world example of how itworks.</p><p>Let's say there's a gatekeeper at the NeetoCal office. This gatekeeperwill only allow the people who work there. It means the gatekeeper has a list ofapproved people and for each person getting through the gate, the gatekeeperchecks if the person is in the approved list or not.</p><p>SPF works similarly. SPF policy is a mechanism to tell the email server if theemail is coming from a trusted source, then accept the email or reject the email.A sender policy framework (SPF) record is a type of DNS TXT record that listsall the servers authorized to send emails from a particular domain.</p><p>Let's take a real-world example. The SPF record of neetocal.com looks like<code>v=spf1 include:spf.messagingengine.com -all</code>. We can see this data<a href="https://mxtoolbox.com/SuperTool.aspx?action=spf%3aneetocal.com&amp;run=toolpage">using mxtoolbox</a>.</p><h3>How the SPF record is used</h3><p>Let's look at the following case.</p><p>Step 1. <em>notifications@neetocal.com</em> sends an email to <em>elon@gmail.com</em>.</p><p>Step 2. This email is sent to <a href="https://fastmail.com">Fastmail</a> since NeetoCal isusing the Fastmail services.</p><p>Step 3. The Fastmail email server receives this email as an outgoing email.</p><p>Step 4. Fastmail email server adds email header <em>Return-path</em> value to&quot;notifiations@neetocal.com&quot;.</p><p>Step 5. The Fastmail email server sends this email to the gmail server.</p><p>Step 6. The Gmail server gets this email.</p><p>Step 7. The Gmail server extracts the <em>Return-path</em> key and finds that thedomain is &quot;neetocal.com&quot;.</p><p>Step 8. The Gmail server finds the TXT DNS records of &quot;neetocal.com&quot;. This listsall the approved IP addresses.</p><p>Step 9. The Gmail server will check if the email server which sent the email isin the approved IP addresses or not.</p><p>Step 10. If the IP address is in the approved IP addresses list then the emailis approved for further processing. Otherwise, the email is rejected.</p><p>We can go to https://dmarcian.com/spf-survey/ and enter &quot;neetocal.com&quot; here.They get the IP addresses published for &quot;messagingengine.com&quot; and then they showthe list of the approved IPs.</p><h3>Allowing the third party to send emails on your behalf</h3><p>Let's say that Brian decides to use <a href="https://www.mailerlite.com">Mailerlite</a> tosend marketing emails. Now if Mailerlite sends an email then the receiving emailserver will notice that the IP address of Mailerlite is not in the approved listof ips and the email will be rejected.</p><p>A domain is allowed to have only one SPF record. So if NeetoCal wants to useMailerlite for marketing then the spf records need to be updated. If you signupfor a domain in Mailerlite then Mailerlite will check if that domain has anexisting SPF record or not. If there is no SPF record then Mailerlite won't doanything. However, if there is an existing SPF record then Mailerlite willinsist that first you update the SPF record to include Mailerlite so that theemails from Mailerlite are not rejected.</p><p><a href="https://bigbinary.com">BigBinary</a> uses Mailerlite to email newly publishedblogs to the subscribers. Given below is what the SPF record of BigBinary lookslike. We can also see this result<a href="https://mxtoolbox.com/SuperTool.aspx?action=spf%3abigbinary.com&amp;run=toolpage">online</a>.</p><pre><code>v=spf1 include:_spf.mlsend.com include:_spf.google.com -all</code></pre><p>BigBinary uses google workspace so the second include is for that reason. Thefirst include is to ensure that Mailerlite is in the allowed ip list.</p><p>If we look at the result for &quot;bigbinary.com&quot; by visitinghttps://dmarcian.com/spf-survey/ then we will that all these includes are likeregular programming language &quot;imports&quot;. They allow the third party to includeother third parties in the chain. The end result is that we have a finite listof approved IP addresses which can send email.</p><h2>spf record standard</h2><p>Let's take a look at NeetoCal's SPF record.</p><pre><code>v=spf1 include:spf.messagingengine.com -all</code></pre><p><code>v=spf1</code> tells the server that this record contains an SPF record. Every SPFrecord must begin with this string.</p><p><code>include:spf.messagingengine.com</code> tells the server what third-partyorganizations are authorized to send emails on behalf of the domain. This tagsignals that the content of the SPF record for the included domain(messagingengine.com in this case) should be checked and the IP addresses itcontains should also be considered authorized. Multiple domains can be includedwithin an SPF record.</p><p><code>-all</code> tells the server that addresses not listed in the SPF record is notauthorized to send emails and should be rejected. Alternative options hereinclude <code>~all</code>, which states that unlisted emails will be marked as insecure orspam but still accepted. <code>+all</code> signifies that any server can send emails onbehalf of the domain.</p><p>Here is another format of SPF record.</p><pre><code>v=spf1 ip4=192.0.2.0 ip4=192.0.2.1 include:messagingengine.email -all</code></pre><p>In this example, the SPF record is telling the server that ip4=192.0.2.0 andip4=192.0.2.1 are also authorized to send emails on behalf of the domain.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Redirecting URL using cloudflare redirect rules]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/redirecting-url-using-cloudflare"/>
      <updated>2023-03-14T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/redirecting-url-using-cloudflare</id>
      <content type="html"><![CDATA[<p>At BigBinary, we had been using AceInvoice as our time tracking and invoicingtool for years. Last year, we migrated all the data to<a href="https://neeto.com/neetoinvoice">NeetoInvoice</a>.</p><p>All this time, the AceInvoice website was not redirecting to NeetoInvoice. Todaywe did that using <a href="https://cloudflare.com">Cloudflare</a>. The URL forwarding orredirecting with<a href="https://developers.cloudflare.com/support/page-rules/configuring-url-forwarding-or-redirects-with-page-rules/">page rule</a>is a neat feature of Cloudflare. Below are the screenshots of the steps taken toredirect the URLs. More details are covered in the video.</p><h3>Handling www version</h3><p><img src="/blog/images/images_used_in_blog/2023/redirecting-url-using-cloudflare/www-version.png" alt="WWW version"></p><h3>Handling no www version</h3><p><img src="/blog/images/images_used_in_blog/2023/redirecting-url-using-cloudflare/no-www-version.png" alt="No WWW version"></p><p>&lt;iframewidth=&quot;560&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/R6-qnYN6PUs&quot;title=&quot;YouTube video player&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote><p>We didn't mention it in the video, but it's worth knowing that we need to have a<code>CNAME</code> record for a subdomain that needs forwarding. For examples let's saythat we need to forward all traffic from <code>https://videos.bigbinary.com</code> to<code>https://bigbinary.com/video</code>. We can't add a page rule for this directly. Forwe need to add a DNS entry for subdomain <code>videos</code> and this entry must havecloudflare &quot;proxy&quot; checked so that you see <code>Proxied</code> next to it. If you see &quot;DNSonly&quot; then that means Cloudflare will not be able to do any forwarding.</p>]]></content>
    </entry><entry>
       <title><![CDATA[How to use JWT to secure your GitHub OAuth callback endpoint]]></title>
       <author><name>Jagannath Bhat</name></author>
      <link href="https://www.bigbinary.com/blog/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint"/>
      <updated>2023-03-07T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint</id>
      <content type="html"><![CDATA[<p>JSON Web Tokens (JWTs) have become a popular way to manage user authenticationand authorization. In this blog, we will explore how to use JWTs in GitHub OAuthprocess, including how to encode additional parameters in the JWT to improve thesecurity and functionality of your GitHub OAuth integration. Let's start bylooking into what OAuth is.</p><h2>OAuth 2.0</h2><p>OAuth 2.0 is an authorization framework that enables an application to accessdata from a server on behalf of a user. For example, OAuth enables applicationsto access data from Google, Facebook, GitHub, etc., on behalf of users havingaccounts in that service.</p><p>Let's say you have an application that requires access to a user's data onGitHub. The following are the steps involved in authorizing your applicationwith GitHub:</p><ol><li><p>The application requests authorization from GitHub. The following are some ofthe parameters this request should contain:</p><ul><li><code>client_id</code> - This is an ID used by GitHub for identifying the application.You need to register your application with GitHub to get this ID.</li><li><code>redirect_uri</code> - The URI to which GitHub should send back a request oncethe authorization is approved.</li><li><code>login</code> - This is a username on GitHub. The application requires access todata on behalf of the user with this username.</li><li><code>state</code> - This should ideally be a string of random characters. Store thisstring in memory because it will be used in another step. This parameter isoptional but highly recommended. We'll look into why later.</li></ul></li><li><p>GitHub then asks the user to grant the authorization. The user might beprompted to log in to GitHub if they have not already logged in. Then GitHubdisplays information on the application and lists all the data theapplication wants to access. If the user denies authorization, the processends here. If the user approves, we move on to the next step.</p></li><li><p>GitHub sends a request to the application through the URI passed as<code>redirect_uri</code> in the first step. This request will contain a <code>code</code>parameter that serves as a temporary authorization code and a <code>state</code>parameter.</p></li><li><p>The OAuth process must be dropped immediately if the value of the <code>state</code>parameter from the previous step does not match the random string stored inmemory in step 1. This ensures that the OAuth process was initiated by yourapplication. We'll look more into this later.</p></li><li><p>The application should send another request to GitHub to generate a permanentauthentication token. This request should contain the <code>code</code> sent by GitHubin step 3.</p></li><li><p>GitHub responds with an authentication token that can be used by theapplication to access data on behalf of the user.</p></li></ol><p><img src="/blog/images/images_used_in_blog/2023/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint/github_oauth_process.png" alt="GitHub OAuth process"></p><h2>The state parameter</h2><p>The <code>state</code> parameter plays a crucial role in ensuring that the GitHub OAuthprocess was initiated by your application or a trusted source. An unauthorizedthird party could pose as your application by using your <code>client_id</code>. The valueof <code>client_id</code> is not exactly a secret. GitHub treats any OAuth processinitiated using your ID as an OAuth process started by your application.</p><p>Let's see how the OAuth process would play out when initiated by an unauthorizedthird party:</p><ol><li><p>Third-party requests authorization from GitHub. The third party would passyour ID as <code>client_id</code>. This would lead GitHub to believe the request is fromyour application. The third party may or may not pass a <code>state</code> parameter.</p></li><li><p>GitHub then asks the user to grant the authorization. If the third party usesone of their own accounts, they could grant the authorization. The thirdparty could also convince a user to grant permission using socialengineering. For example, they could perform a phishing attack using an emaildesigned to trick the user into believing that the email was from yourapplication.</p></li><li><p>GitHub sends a request to the application through the URI passed as<code>redirect_uri</code> in the first step. This request would contain the <code>code</code>parameter, and the <code>state</code> parameter if it was passed in step 1.</p></li><li><p>It would be clear that the process was not initiated by your application ifthe <code>state</code> parameter is missing. Even if there was a <code>state</code> parameter, itwould not be found in memory. This is because the state parameter wasgenerated by a third party and not your application. Only those stateparameters generated by your application will be found in your memory.</p></li></ol><h2>JSON Web Tokens</h2><p>JSON Web Tokens (JWT) is a structured format that contains header, payload, andsignature components. Let's take a look at the significance of these components:</p><ol><li><p><strong>Header</strong> - The header is a JSON string that typically contains the signingalgorithm used, such as HMAC, SHA256, or RSA.</p></li><li><p><strong>Payload</strong> - The payload is a JSON string that contains claims andadditional data. Claims can be used to enforce security constraints andvalidate the authenticity of the token and its contents. For example, aclaims can specify the token expiration time. The expiration time, mostcommonly represented by <code>exp</code>, represents the date and time after which thetoken will no longer be considered valid.</p></li><li><p><strong>Signature</strong> - The signature is used to verify that the sender of the JWT iswho it says it is and to ensure that the token has not been tampered withalong the way. Just like the hand signature of a person, it is hard to forgea JWT signature. (Digital signatures are significantly harder to forgecompared to a person's hand signature)</p></li></ol><h3>Generating a JWT</h3><p>The following steps outline the process of generating a JWT:</p><ol><li><p>The header and the payload of the JWT are first encoded using Base64encoding. So now we have two strings - the encoded header and the encodedpayload.</p></li><li><p>The encoded header and the encoded payload are concatenated into a singlestring, with dots (.) separating each part. The concatenated string is signedusing the signing algorithm specified in the header. The signing algorithmuses a secret key, that is known only to the issuer (your application), whichensures that only the issuer can generate a valid signature. The result ofthe signing process is a signature, which is also a string.</p></li><li><p>The encoded header, the encoded payload, and the signature are concatenatedinto a single string, with dots (.) separating each part. The resultingstring is the JWT, which can be transferred securely between parties.</p></li></ol><p>For example, let's say we have the following header:</p><pre><code class="language-json">{ &quot;alg&quot;: &quot;RS256&quot; }</code></pre><p>The value of <code>alg</code> contains the algorithm used for signing the JWT. Also, let'ssay we have the following payload:</p><pre><code class="language-json">{ &quot;username&quot;: &quot;sam@example.com&quot;, &quot;exp&quot;: 1676263763 }</code></pre><p>The <code>username</code> is data that needs to be transferred. <code>exp</code> contains theexpiration time claim of the JWT.</p><p>When both these components are encoded, we get:</p><ul><li>Header - &quot;eyJhbGciOiJSUzI1NiJ9&quot;</li><li>Payload - &quot;eyJ1c2VyIjoic2FtQGV4YW1wbGUuY29tIiwiZXhwIjoxNjc2MjYzNzYzfQ&quot;</li></ul><p>When these are signed using the RS256 algorithm and a secret key, a signature isproduced. The following signature was generated using the RS256 algorithm and asecret key (which will remain secret):</p><pre><code class="language-text">NlAT6awp68dCEcFXbDeeLTzZekqUmB3f6kr3jkGSFmrKa5zvLmFGeraWba_fUuQLVhRtcXUPZbRR1DKnKH0HVf1rRDvOqezwbhe-hR1wlz6vZkHuPjtYSCLx_aybGm7dy2ijfTQwYd14cD9ZiMI5vf6XcDDfE7mkhu0ogCOnqR1v3KOEWJkMkvGBHfHKuf9FKYbWltHtUE6bAEO1orq0JayD8UNUKxdGkElXA7mkuIEexmBuieG9PJ2ow_uo05QCsqDvxlzOCMMIe7WdT7gmz4myiZ7lVuUcL1V2-Y1PJqWDyqDZbKNxd4X_CwW0RLOF1pw9S2URgybqHZFG0murNw</code></pre><p>So the final JWT would be:</p><p><img src="/blog/images/images_used_in_blog/2023/how-to-use-jwt-to-secure-your-github-oauth-callback-endpoint/jwt_decoded.png" alt="Screenshot of decoded JWT"></p><p>The image above was captured from the decoding tool in<a href="https://jwt.io/">jwt.io</a>.</p><p>JWT is basically a Base64 encoded string with a signature attached to it. Havingthat signature component makes JWT a secure format for transferring data. It ispossible to simply encode data with Base64 and transfer that string. From theexample above, transferring the encoded payload&quot;eyJ1c2VyIjoic2FtQGV4YW1wbGUuY29tIiwiZXhwIjoxNjc2MjYzNzYzfQ&quot; can also get thedata to the other party. However, the party that receives the data have no wayof ensuring that the data was sent by a trusted source and that the data was nottampered with along the way.</p><h3>Verifying JWT tokens</h3><p>Anyone who has the secret key used to sign a JWT, can verify the integrity ofthe JWT. The steps involved in verifying the signature of a JSON Web Token (JWT)are:</p><ol><li><p>Split the JWT into the encoded header, the encoded payload and the signature,using the dot (.) used to separate the three components.</p></li><li><p>Decode the encoded header and the encoded payload using Base64 to get theheader and payload JSON strings.</p></li><li><p>The application recreates the signature by signing the encoded header and theencoded payload using the signing algorithm in the header and the secret key.If the signature created in this step is the same as the signature in theJWT, the JWT is valid.</p></li><li><p>The application validates the claims in the payload if there are any.</p></li></ol><p>It can be verified that the JWT was generated by your application and has notbeen tampered with, if the signature is valid. If the JWT header or payload wastampered with, the signature produced while verifying would be different fromthe one in the JWT.</p><h2>Using JWT for the state parameter</h2><p>We can generate a JWT token and pass that as the state parameter in the GitHubOAuth authorizing process. Here's how the process will be different when usingJWT:</p><ol><li><p>The application requests authorization from GitHub. This time the <code>state</code>parameter will be a JWT signed using a secure algorithm and a secret key. TheJWT need not be stored in memory.</p></li><li><p>GitHub gets the approval of the user.</p></li><li><p>GitHub sends a request to the application through the URI passed as<code>redirect_uri</code> in the first step. This request will contain the <code>code</code> andthe <code>state</code> parameters. Here, the state parameter would be a JWT.</p></li><li><p>Validate the JWT from the <code>state</code> parameter. If the JWT is invalid, drop theauthorization process immediately.</p></li></ol><h2>Advantages of using JWT for the state parameter</h2><ol><li><p><strong>No Storage requirement</strong> - When using a random string for the stateparameter, that string has to be stored, so that it can be used later forverification. However, JWTs can be verified without storing them once theyare generated.</p></li><li><p><strong>Ability to send additional data</strong> - The payload component of JWT can beused to send additional data such as user data, permissions data, etc.</p></li><li><p><strong>Security</strong> - Using JWT can ensure that the OAuth process was initiated byyour application or a trusted source. In addition, JWT also ensures theintegrity of the data. This means that we can ensure that the payload in theJWT has not been tampered with.</p></li><li><p><strong>Flexibility</strong> - The claims in the payload component of JWT can be usedfurther enhance the security of the JWT by implementing custom securitychecks based on the claims in the payload.</p></li><li><p><strong>Standardization</strong> - JWT is a widely used format for transferring data.Using JWT would be beneficial when there are different applications andservices involved in the OAuth process.</p></li></ol><h2>References</h2><ol><li><p>OAuth 2.0 -<a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2">https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2</a></p></li><li><p>GitHub OAuth Authorization process -<a href="https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps">https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps</a></p></li><li><p>Phishing Attack -<a href="https://www.imperva.com/learn/application-security/phishing-attack-scam/">https://www.imperva.com/learn/application-security/phishing-attack-scam/</a></p></li></ol>]]></content>
    </entry><entry>
       <title><![CDATA[Setting up Heroku DNS using cloudflare]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/setting-up-heroku-dns-using-clouflare"/>
      <updated>2022-09-26T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/setting-up-heroku-dns-using-clouflare</id>
      <content type="html"><![CDATA[<p>Lots of folks know <a href="https://www.cloudflare.com/">cloudflare</a> for the DDoSprotection, rate limiting etc services it provides. Here at BigBinary, we alsouse Cloudflare for DNS management.</p><p>DNS management is a free service by Cloudflare. However, on first glance, itmight not appear that it's a free service. Once we add a site, then we see ascreen like this. Here, we need to remember to scroll down to see the freeoption.</p><p><img src="/blog/images/images_used_in_blog/2022/setting-up-heroku-dns-using-clouflare/pricing.png" alt="cloudflare pricing page"></p><p>Now let's see how we can map the DNS settings from Heroku to Cloudflare. We willlook at both a standard domain name and then we will take a look at a wildcarddomain name.</p><h3>Standard domain name</h3><p>We are hosting <a href="https://www.gitemit.com/">GitEmit</a> using Heroku. We are lettingHeroku manages the SSL for this domain.</p><p>After setting up domains in Heroku, here is what we see.</p><p><img src="/blog/images/images_used_in_blog/2022/setting-up-heroku-dns-using-clouflare/heroku-dns-gitemit.png" alt="Heroku DNS gitemit"></p><p>In Cloudflare, we can set it up using two CNAMEs. It looks like this.</p><p><img src="/blog/images/images_used_in_blog/2022/setting-up-heroku-dns-using-clouflare/cloudflare-heroku-gitemit.png" alt="Heroku DNS"></p><h3>Wild card domain name</h3><p>We are hosting <a href="https://www.neeto.com/neetochat/">NeetoChat</a> application usingHeroku.</p><p>Since it's a wild card domain, we had to<a href="https://www.bigbinary.com/blog/wild-card-ssl-on-heroku">generate the certificates</a>ourselves.</p><p>After setting up domains in Heroku, here is what we see.</p><p><img src="/blog/images/images_used_in_blog/2022/setting-up-heroku-dns-using-clouflare/heroku-dns-neetochat.png" alt="Heroku DNS NeetoChat"></p><p>In Cloudflar,e we can set it up using three CNAMEs. It looks like this.</p><p><img src="/blog/images/images_used_in_blog/2022/setting-up-heroku-dns-using-clouflare/cloudflare-heroku-neetochat.png" alt="Heroku DNS"></p>]]></content>
    </entry><entry>
       <title><![CDATA[Configure cypress to run tests in multiple environments]]></title>
       <author><name>Datt Dongare</name></author>
      <link href="https://www.bigbinary.com/blog/cypress-environment-config"/>
      <updated>2022-03-30T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/cypress-environment-config</id>
      <content type="html"><![CDATA[<p>When we write tests, we want to run them in the local environment first. There arefew reasons for this. We need to add <code>data-cy</code>, verify and run the tests. Also,we need to make sure that tests are running fine on the local/developmentenvironment before running them in test/staging environment. Another case mightbe the one where we need to set the CI pipeline to execute in differentenvironments.</p><p>In such scenarios, the <code>baseUrl</code> will be different in both environments. Butcypress allows us to configure <code>baseUrl</code> only once in <code>cypress.json</code> . Then, howdo we configure <code>baseUrl</code> specific for each of the environments? In this blog,we will see how to configure cypress step by step so that we can run cypresstests in multiple environments.</p><h2>1. Add environment specific configuration</h2><p>To configure Cypress for different environments, we can follow the two simplesteps.</p><ol><li>Create <code>config</code> folder in cypress.</li><li>Create separate files for each of the environments.</li></ol><pre><code class="language-javascript">// cypress/config/cypress.development.json{  &quot;baseUrl&quot;: &quot;https://localhost:9006&quot;,  &quot;env&quot;: {    &quot;environment&quot;: &quot;development&quot;  },  &quot;execTimeout&quot;: 18000,  &quot;defaultCommandTimeout&quot;: 300000,  &quot;requestTimeout&quot;: 10000,  &quot;pageLoadTimeout&quot;: 30000,  &quot;responseTimeout&quot;: 10000,  &quot;viewportWidth&quot;: 1200,  &quot;viewportHeight&quot;: 1200,  &quot;videoUploadOnPasses&quot;: false,  &quot;retries&quot;: {    &quot;runMode&quot;: 1,    &quot;openMode&quot;: 2  }}</code></pre><pre><code class="language-javascript">// cypress/config/cypress.test.json{  &quot;baseUrl&quot;: &quot;https://test.example.com&quot;,  &quot;env&quot;: {    &quot;environment&quot;: &quot;test&quot;  },  &quot;execTimeout&quot;: 300000,  &quot;defaultCommandTimeout&quot;: 60000,  &quot;requestTimeout&quot;: 20000,  &quot;pageLoadTimeout&quot;: 60000,  &quot;responseTimeout&quot;: 20000,  &quot;viewportWidth&quot;: 1200,  &quot;viewportHeight&quot;: 1200,  &quot;videoUploadOnPasses&quot;: true,  &quot;retries&quot;: {    &quot;runMode&quot;: 2,    &quot;openMode&quot;: 1  }}</code></pre><pre><code class="language-javascript">// cypress/config/cypress.production.json{  &quot;baseUrl&quot;: &quot;https://live.example.com&quot;,  &quot;env&quot;: {    &quot;environment&quot;: &quot;production&quot;  },  &quot;execTimeout&quot;: 300000,  &quot;defaultCommandTimeout&quot;: 60000,  &quot;requestTimeout&quot;: 20000,  &quot;pageLoadTimeout&quot;: 60000,  &quot;responseTimeout&quot;: 20000,  &quot;viewportWidth&quot;: 1200,  &quot;viewportHeight&quot;: 1200,  &quot;videoUploadOnPasses&quot;: true,  &quot;retries&quot;: {    &quot;runMode&quot;: 2,    &quot;openMode&quot;: 1  }}</code></pre><h2>2. Initialize config files</h2><p>After adding config folder, we need to tell cypress about those config files. Wecan do that by updating <code>plugins/index.js</code> with following code. This file getsexecuted after we start the cypress server.</p><pre><code class="language-javascript">// cypress/plugins/index.jsconst fs = require(&quot;fs-extra&quot;);const path = require(&quot;path&quot;);const fetchConfigurationByFile = file =&gt; {  const pathOfConfigurationFile = `config/cypress.${file}.json`;  return (    file &amp;&amp; fs.readJson(path.join(__dirname, &quot;../&quot;, pathOfConfigurationFile))  );};module.exports = (on, config) =&gt; {  const environment = config.env.configFile || &quot;development&quot;;  const configurationForEnvironment = fetchConfigurationByFile(environment);  return configurationForEnvironment || config;};</code></pre><p>In the above code, <code>Cypress</code> loads the configuration file based on theenvironment. When we run <code>Cypress</code> we can pass environment variables. In thiscase, we need to pass <code>configFile</code> as an environment variable. If we don't pass<code>configFile</code>, by default <code>Cypress</code> will consider <code>development</code> as the currentenvironment.</p><h2>3. Setup scripts</h2><p>Cypress can accept command-line arguments. We can set the environment by passingthese arguments in the <code>cypress run</code> or <code>cypress open</code> command e.g.<code>cypress open --env configFile=test</code>. This command looks lengthy. Also,sometimes we need to pass more command-line arguments along with <code>configFile</code>.We can create short and handy commands by configuring the <code>package.json</code>.</p><p>For example: <code>yarn run cy:open:dev</code>.</p><pre><code class="language-javascript">// cypress/package.json&quot;cy:open:dev&quot;: &quot;cypress open --env configFile=development&quot;,&quot;cy:open:dev:chrome&quot;: &quot;cypress open --browser chrome --env configFile=development&quot;,&quot;cy:run:dev&quot;: &quot;cypress run --env configFile=development&quot;,&quot;cy:open:staging&quot;: &quot;cypress open --env configFile=test&quot;,&quot;cy:run:staging&quot;: &quot;cypress run --env configFile=test&quot;,</code></pre><h2>4. Precedence of configuration</h2><p>By default, we have <code>cypress.json</code> in Cypress where some config can be added.The precedence of configuration is higher for the files in the <code>config</code> folderthan the <code>cypress.json</code>. So if we have some key <code>execTimeout</code> defined in both<code>cypress.json</code> and <code>cypress.test.json</code>. The value of <code>execTimeout</code> in the<code>cypress.test.json</code> will be considered.</p><p>Generally, the rule of thumb is that a common configuration for all theenvironments can be kept in the <code>cypress.json</code>. The environment-specific configcan be kept in <code>config/</code> folder.</p><h2>Conclusion</h2><p>We saw that how we can configure the cypress in multiple environments. In thisway we can run Cypress for each environment without much hassle. This definitelyhelps us in running Cypress in both local as well as in the productionenvironment.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Using Cookies with Postgraphile]]></title>
       <author><name>Agney Menon</name></author>
      <link href="https://www.bigbinary.com/blog/cookies-with-postgraphile"/>
      <updated>2021-06-01T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/cookies-with-postgraphile</id>
      <content type="html"><![CDATA[<p>This blog details usage of cookies on a Postgraphile-based application. We willbe using Postgraphile with Express for processing the cookies, but any similarlibrary can be used.</p><p>Cookies can be a very safe method for storage on the client side. They can beset as:</p><ul><li>HTTP only: cannot be accessed through client-side JavaScript, saving it fromany third party client-side scripts or web extensions.</li><li>Secure: The web browser ensures that the cookies are set only on a <em>secure</em>channel.</li><li>Signed: We can sign the content to make sure it isn't changed on the clientside.</li><li>Same Site: Make sure that the cookie is sent only if the site matches yourdomain/subdomain (<a href="https://web.dev/samesite-cookies-explained/">details</a>)</li></ul><h2>Prerequisites</h2><ul><li>Postgraphile - Generates an instant GraphQL API from a Postgres database</li><li>Express - Minimalistic backend framework for NodeJS</li></ul><h2>Setup</h2><p>We will start off with a base Express setup generated with<a href="https://expressjs.com/en/starter/generator.html">express-generator</a>.</p><pre><code class="language-javascript">const createError = require(&quot;http-errors&quot;);const express = require(&quot;express&quot;);const path = require(&quot;path&quot;);const cookieParser = require(&quot;cookie-parser&quot;);const logger = require(&quot;morgan&quot;);const app = express();require(&quot;dotenv&quot;).config();app.use(logger(&quot;dev&quot;));app.use(express.json());app.use(express.urlencoded({ extended: false }));app.use(express.static(path.join(__dirname, &quot;public&quot;)));// Use secret key to sign the cookies on creation and parsingapp.use(cookieParser(process.env.SECRET_KEY));// Catch 404 and forward to error handlerapp.use(function (req, res, next) {  next(createError(404));});// Error handlerapp.use(function (err, req, res) {  // Set locals, only providing error in development  res.locals.message = err.message;  res.locals.error = req.app.get(&quot;env&quot;) === &quot;development&quot; ? err : {};  // Render the error page  res.status(err.status || 500);  res.render(&quot;error&quot;);});module.exports = app;</code></pre><p>From<a href="https://www.graphile.org/postgraphile/usage-library/">Postgraphile's usage library page</a>for adding Postgraphile to an express app:</p><pre><code class="language-javascript">app.use(  postgraphile(    process.env.DATABASE_URL || &quot;postgres://user:pass@host:5432/dbname&quot;,    &quot;public&quot;,    {      watchPg: true,      graphiql: true,      enhanceGraphiql: true,    }  ));</code></pre><p>Now for the table setup. We need a private <code>user_accounts</code> table and a methodnamed <code>authenticate_user</code> that will return a JWT token of the form:</p><pre><code>{  token: 'jwt_token_here',  username: '',  ...anyOtherDetails}</code></pre><p>We will not be detailing table creation or authentication as there are many waysto go about it. But if you need help,<a href="https://www.graphile.org/postgraphile/security/">Postgraphile security</a> is thepage to rely on.</p><h2>Adding the Plugin library</h2><p>To attach a cookie to the request, we will use the <code>@graphile/operation-hooks</code>library which is open-sourced<a href="https://github.com/graphile/operation-hooks">on Github</a>.</p><pre><code class="language-bash">npm install @graphile/operation-hooks# ORyarn add @graphile/operation-hooks</code></pre><p>To add the library to the app:</p><pre><code class="language-javascript">const { postgraphile, makePluginHook } = require(&quot;postgraphile&quot;);const pluginHook = makePluginHook([  require(&quot;@graphile/operation-hooks&quot;).default,  // Any more PostGraphile server plugins here]);app.use(  postgraphile(    process.env.DATABASE_URL || &quot;postgres://user:pass@host:5432/dbname&quot;,    &quot;public&quot;,    {      watchPg: true,      graphiql: true,      enhanceGraphiql: true,      pluginHook,      appendPlugins: [        // You will be adding the hooks here      ],    }  ));</code></pre><h2>Adding the Plugin</h2><p>The plugin allows for two different types of hooks:</p><ol><li><a href="https://github.com/graphile/operation-hooks#sql-hooks">SQL Hooks</a></li><li><a href="https://github.com/graphile/operation-hooks#implementing-operation-hooks-in-javascript">JavaScript Hooks</a></li></ol><p>Since accessing cookies is a JavaScript operation, we will be concentrating onthe second type.</p><p>To hook the plugin into the build system, we can use the <code>addOperationHook</code>method.</p><pre><code class="language-javascript">module.exports = function OperationHookPlugin(builder) {  builder.hook(&quot;init&quot;, (_, build) =&gt; {    // Register our operation hook (passing it the build object):    // setAuthCookie is a function we will define later.    build.addOperationHook(useAuthCredentials(build));    // Graphile Engine hooks must always return their input or a derivative of    // it.    return _;  });};</code></pre><p>If this is contained in a file named <code>set-auth-cookie.js</code>, then the plugin canbe added to the append plugins array as follows:</p><pre><code class="language-javascript">{  appendPlugins: [    require('./set-auth-cookie.js'),  ],}</code></pre><h2>Designing the hook</h2><p>The function to be executed receives two arguments: <code>build</code> process and thecurrent <code>fieldContext</code>.</p><p>The <code>fieldContext</code> consists of fields that can be used to narrow down themutation or query that we want to target; e.g. if the hook is to run only onmutations, we can use the <code>fieldContext.isRootMutation</code> field.</p><pre><code class="language-javascript">const useAuthCredentials = build =&gt; fieldContext =&gt; {  const { isRootMutation } = fieldContext;  if (!isRootMutation) {    // No hook added here    return null;  }};</code></pre><p>To direct the system on usage of the plugin, we have to return an object with<code>before</code>, <code>after</code> or <code>error</code> fields. Here is how these keywords can be used:</p><p>(comments are from<a href="https://github.com/graphile/operation-hooks-example/blob/master/hooks/logger.js">the example repository</a>)</p><pre><code class="language-javascript">return {  // An optional list of callbacks to call before the operation  before: [    // You may register more than one callback if you wish. They will be mixed in with the callbacks registered from other plugins and called in the order specified by their priority value.    {      // Priority is a number between 0 and 1000. If you're not sure where to put it, then 500 is a great starting point.      priority: 500,      // This function (which can be asynchronous) will be called before the operation. It will be passed a value that it must return verbatim. The only other valid return is `null` in which case an error will be thrown.      callback: logAttempt,    },  ],  // As `before`, except the callback is called after the operation and will be passed the result of the operation; you may return a derivative of the result.  after: [],  // As `before`; except the callback is called if an error occurs; it will be passed the error and must return either the error or a derivative of it.  error: [],};</code></pre><p>Since we want our action to happen after we get result from the mutation, wewill add it to the <code>after</code> array.</p><pre><code class="language-javascript">const useAuthCredentials = build =&gt; fieldContext =&gt; {  const { isRootMutation, pgFieldIntrospection } = fieldContext;  if (!isRootMutation) {    // No hook added here    return null;  }  if (    !pgFieldIntrospection ||    // Name of the mutation is authenticateUser    pgFieldIntrospection.name !== &quot;authenticateUser&quot;  ) {    // narrowing the scope down to the mutation we want    return null;  }  return {    before: [],    after: [      {        priority: 1000,        callback: (result, args, context) =&gt; {          // The result is here, so we can access accessToken and username.          console.log(result);        },      },    ],    error: [],  };};</code></pre><p>Since the functionality is inside the plugin hook, we do not have the expressresult to set the cookie .</p><p>But we do have an escape hatch with the third argument: <code>context</code>. Postgraphileallows us to pass functions or values into the context variable from thepostgraphile instance.</p><pre><code class="language-javascript">app.use(  postgraphile(process.env.DATABASE_URL, &quot;public&quot;, {    async additionalGraphQLContextFromRequest(req, res) {      return {        // Function to set the cookie passed into the context object        setAuthCookie: function (authCreds) {          res.cookie(&quot;app_creds&quot;, authCreds, {            signed: true,            httpOnly: true,            secure: true,            // Check if you want to include SameSite cookies here, depending on your hosting.          });        },      };    },  }));</code></pre><p>We can now set the cookie inside the plugin hook.</p><pre><code class="language-javascript">{  priority: 1000,  callback: (result, args, context) =&gt; {    // This function is passed from additionalGraphQLContextFromRequest as detailed in the snippet above    context.setAuthCookie(result);  }}</code></pre><h2>Reading from the Cookie </h2><p>We have already added the <code>cookieParser</code> with <code>SECRET_KEY</code>, so express willparse the cookies for us.</p><p>But we probably want them to be accessible inside SQL functions forPostgraphile. That is how we can determine if the user is signed in or whattheir permissions are. To do that, Postgraphile provides a <code>pgSettings</code> object.</p><pre><code class="language-javascript">app.use(  postgraphile(process.env.DATABASE_URL, &quot;public&quot;, {    pgSettings: async req =&gt; ({      user: req.signedCookies[&quot;app_creds&quot;],    }),  }));</code></pre><p>Inside an SQL function, the variables passed from settings can be accessed likethis:</p><pre><code class="language-sql">current_setting('user')</code></pre><hr><p>That's all . We can store any details in cookies, retrieve them on the Expressend and use them inside Postgres functions for authentication or authorization.</p><p>Check out <a href="https://github.com/graphile/operation-hooks">operation-hooks</a> pluginfor more details.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Authorization in REST vs Postgraphile]]></title>
       <author><name>Amal Jose</name></author>
      <link href="https://www.bigbinary.com/blog/authorization-in-rest-vs-postgraphile"/>
      <updated>2021-01-20T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/authorization-in-rest-vs-postgraphile</id>
      <content type="html"><![CDATA[<p><a href="https://www.graphile.org/postgraphile/">Postgraphile</a> is a great tool formaking instant GraphQL from a PostgreSQL database. When I started working withPostgraphile, its authorization part felt a bit different compared to the RESTbased backends which I had worked with before. Here I will share somedifferences that I noted.</p><p>First, let's see <strong>Authentication</strong> vs <strong>Authorization</strong>.</p><p><strong>Authentication</strong> is determining whether a user is logged in or not.<strong>Authorization</strong> is then deciding what the users has permission to do or see.</p><h2>Comparing the implementation of a blog application using Postgraphile vs REST</h2><p>Suppose we have to build a blog application with the below schema.</p><p><img src="/blog/images/images_used_in_blog/2021/authorization-in-rest-vs-postgraphile/blog-application.png" alt="event delegation"></p><h6>Features of the blog application.</h6><ul><li><p>Display <strong>published</strong> blogs with <strong>is_published = true</strong> to all users.</p></li><li><p>Display <strong>unpublished</strong> blogs with <strong>is_published = false</strong> to its creatoronly.</p></li></ul><h2>REST Implementation</h2><p>The REST implementation with JavaScript and<a href="https://sequelize.org/master/manual/model-querying-basics.html">sequelize</a> canbe like below.</p><p><img src="/blog/images/images_used_in_blog/2021/authorization-in-rest-vs-postgraphile/rest-implementation.jpeg" alt="REST implementation"></p><p>The <strong>client</strong> requests the blogs using an endpoint, it also attaches the accesstoken received from the authentication service.</p><pre><code class="language-js">const getBlogs = () =&gt;  requestData({    endpoint: `/api/blogs`,    accessToken: &quot;***&quot;,  });</code></pre><p>The backend code in the <strong>server</strong> receives the request, finds the currentlogged in user from the access token, and requests the data based on the currentlogged in user from the database.</p><pre><code class="language-js">const userEmail = findEmail(accessToken);const blogs = await models.Blogs.findAll({  where: { [Op.or]: [{ creatorEmail: userEmail }, { isPublished: true }] },});res.send(blogs);</code></pre><p>Here, the backend code finds the users email from the access token, thenrequests the database to give the list of blogs that have creatorEmail matchingto the current user's email or the field isPublished is true.</p><p>The <strong>database</strong> will return whatever data the server requests.</p><p>Similarly, for creating, editing, and deleting blogs, we can have differentend-points to handle the authorization logic in the backend code.</p><h2>Postgraphile Implementation</h2><p>The postgraphile implementation can be like below.</p><p><img src="/blog/images/images_used_in_blog/2021/authorization-in-rest-vs-postgraphile/postgraphile-implementation.jpeg" alt="postgraphile implementation"></p><p>The <strong>client</strong> requests the blogs using a GraphQL query. It also attaches theaccess token received from the authentication service.</p><pre><code class="language-js">const data = requestQuery({ query: &quot;allBlogs {         nodes {            content            creatorEmail            visiblityType           }        }&quot; accessToken: '***'})</code></pre><p>In the <strong>server,</strong> we configure Postgraphile to pass the user information to thedatabase.</p><pre><code class="language-js">export postgraphile(DATABASE_URL, schemaName, {  pgSettings: (req) =&gt; {     const userEmail = findEmail(accessToken);     return({         'current_user_email': userEmail     })  }})</code></pre><p>We can pass a function as Postgraphiles pg<a href="https://www.graphile.org/postgraphile/usage-library/#pgsettings-function">Settings</a>property, whose return value will be accessible from the connected Postgresdatabase by calling the current_setting function.</p><p>In the <strong>database,</strong> the row-level security policies can be defined to controlthe data access.</p><p><a href="https://www.postgresql.org/docs/12/ddl-rowsecurity.html">Row-level security policies</a>are basically just SQL that either evaluates to true or false. If a policy iscreated and enabled for a table, that policy will be checked before doing anoperation on the table.</p><pre><code class="language-pgsql">create policy blogs_policy_selecton public.blogs for select to usersUSING ( isPublished OR creator_email = current_setting('current_user_email'));ALTER TABLE blogs ENABLE ROW LEVEL SECURITY;</code></pre><p>Here the policy named <em>blogs_policy_select</em> will be checked before selecting arow in the table <em>public.blogs.</em> A row will be selected only if the<em>isPublished</em> field is <em>true</em> or <em>creator_email</em> matches with the current user'semail.</p><p>Similarly, for creating, editing, and deleting blogs, we can have row levelsecurity policies for INSERT, UPDATE, and DELETE operations on the table.</p><h2>Conclusion</h2><p>The REST implementation does the authorization on the server level but thePostgraphile does it on the database level. Each implementation has its ownadvantages and disadvantages, which is a topic for another day.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Sort query data on associated table in PostGraphile]]></title>
       <author><name>Taha Husain</name></author>
      <link href="https://www.bigbinary.com/blog/sort-query-data-on-associated-tables-in-postgraphile-using-order-by-plugin"/>
      <updated>2021-01-19T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/sort-query-data-on-associated-tables-in-postgraphile-using-order-by-plugin</id>
      <content type="html"><![CDATA[<p><a href="https://www.graphile.org/postgraphile/">PostGraphile</a> provides sorting on allcolumns of a table in a GraqhQL query by default with <code>orderBy</code> argument.</p><p>Although, sorting based on associated tables columns or adding a custom sortcan be achieved via plugins. In this blog we will explore two such plugins.</p><h3>Using <code>pg-order-by-related</code> plugin</h3><p><a href="https://github.com/graphile-contrib/pg-order-by-related">pg-order-by-related</a>plugin allows us to sort query result based on associated table's columns. Itdoes that by adding enums for all associated table's columns. Here's what weneed to do to use this plugin.</p><h4>Installation</h4><pre><code class="language-shell">npm i @graphile-contrib/pg-order-by-related</code></pre><h4>Adding the plugin</h4><pre><code class="language-javascript">const express = require(&quot;express&quot;);const { postgraphile } = require(&quot;postgraphile&quot;);const PgOrderByRelatedPlugin = require(&quot;@graphile-contrib/pg-order-by-related&quot;);const app = express();app.use(  postgraphile(process.env.DATABASE_URL, &quot;public&quot;, {    appendPlugins: [PgOrderByRelatedPlugin],  }));</code></pre><h4>Using associated table column enum with <code>orderBy</code> argument</h4><pre><code class="language-graphql">query getPostsSortedByUserId {  posts: postsList(orderBy: AUTHOR_BY_USER_ID__NAME_ASC) {    id    title    description    author: authorByUserId {      id      name    }  }}</code></pre><p><code>pg-order-by-related</code> plugin is useful only when we want to sort data based onfirst level association. If we want to apply <code>orderBy</code> on second level tablecolumns or so, we have to use <code>makeAddPgTableOrderByPlugin</code>.</p><h3>Using <code>makeAddPgTableOrderByPlugin</code></h3><p><a href="https://www.graphile.org/postgraphile/make-add-pg-table-order-by-plugin/">makeAddPgTableOrderByPlugin</a>allows us to add custom enums that are accessible on specified table's <code>orderBy</code>argument. We can write our custom select queries using this plugin.</p><p>We will use a complex example to understand the use-case of custom <code>orderBy</code>enum.</p><p>In our posts list query, we want posts to be sorted by author's address. Addresshas country, state and city columns. We want list to be sorted by country, stateand city in the same order.</p><p>Here's how we can achieve this using <code>makeAddPgTableOrderByPlugin</code>.</p><p><code>plugins/orderBy/orderByPostAuthorAddress.js</code></p><pre><code class="language-javascript">import { makeAddPgTableOrderByPlugin, orderByAscDesc } from &quot;graphile-utils&quot;;export default makeAddPgTableOrderByPlugin(  &quot;public&quot;,  &quot;post&quot;,  ({ pgSql: sql }) =&gt; {    const author = sql.identifier(Symbol(&quot;author&quot;));    const address = sql.identifier(Symbol(&quot;address&quot;));    return orderByAscDesc(      &quot;AUTHOR_BY_USER_ID__ADDRESS_ID__COUNTRY__STATE__CITY&quot;,      ({ queryBuilder }) =&gt; sql.fragment`(            SELECT              CONCAT(                ${address}.city,                ', ',                ${address}.state,                ', ',                ${address}.country              ) AS full_address            FROM public.user as ${author}            JOIN public.address ${address} ON ${author}.address_id = ${address}.id            WHERE ${author}.id = ${queryBuilder.getTableAlias()}.user_id            ORDER BY ${address}.country DESC, ${address}.state DESC, ${address}.city DESC            LIMIT 1          )`    );  });</code></pre><h4>Export all custom <code>orderBy</code> plugins</h4><p><code>plugins/orderBy/index.js</code></p><pre><code class="language-javascript">export { default as orderByPostAuthorAddress } from &quot;./orderByPostAuthorAddress&quot;;</code></pre><h4>Append custom <code>orderBy</code> plugins to <code>postgraphile</code></h4><pre><code class="language-javascript">const express = require(&quot;express&quot;);const { postgraphile } = require(&quot;postgraphile&quot;);import * as OrderByPlugins from &quot;./plugins/orderby&quot;;const app = express();app.use(  postgraphile(process.env.DATABASE_URL, &quot;public&quot;, {    appendPlugins: [...Object.values(OrderByPlugins)],  }));</code></pre><h4>Using custom enum with <code>orderBy</code> argument</h4><pre><code class="language-graphql">query getPostsSortedByAddress {  posts: postsList(    orderBy: AUTHOR_BY_USER_ID__ADDRESS_ID__COUNTRY__STATE__CITY  ) {    id    title    description    author: authorByUserId {      id      name      address {        id        country        state        city      }    }  }}</code></pre><p>Please head to<a href="https://github.com/graphile-contrib/pg-order-by-related">pg-order-by-related</a>and<a href="https://www.graphile.org/postgraphile/make-add-pg-table-order-by-plugin/">makeAddPgTableOrderByPlugin</a>pages for detailed documentation.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Setting up wild card SSL on heroku]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/wild-card-ssl-on-heroku"/>
      <updated>2020-12-01T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/wild-card-ssl-on-heroku</id>
      <content type="html"><![CDATA[<p>Setting up wild card SSL on heroku can be complicated. Recently I had to set itup for a new domain and this time I recorded the whole process.</p><p>The ssl certificate in this example was bought from namecheap but the sameprocess would apply for other vendors too.</p><p>The video of the whole process is available here.</p><p>&lt;iframewidth=&quot;100%&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/A6URYtDWZhg&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote><h3>Script to generate keys</h3><pre><code class="language-bash">openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr</code></pre><p>When the prompt asks for <code>Common name(full qualified host name)</code> then enter<code>*.yourdomainname.com</code>. Since we are setting up a wild card certificate it'simportant that the common name starts with a <code>*</code>. Otherwise later we are goingto get an error.</p><p>Except the above mentioned question the answer to other questions do not matterat all. You can enter junk values and the SSL will work just fine.</p><p>Hit enter when a challenge password is requested.</p><h3>Script to generate ssl bundle</h3><pre><code class="language-bash">$ cat __neetohelp_net.crt __neetohelp_net.ca-bundle &gt; ssl-bundle.crt</code></pre><p>Note that the order of the crt and bundle files matters when combining them.</p><p>Secondly, as shown in the video, we might have to split the combined line. Nowlet's examine the contents of the combined file.</p><pre><code class="language-bash">$ cat ssl-bundle.crt</code></pre><p>If we see a line like the one below:</p><pre><code class="language-plaintext">-----END CERTIFICATE----------BEGIN CERTIFICATE-----</code></pre><p>Then we need to split the line such that <code>END</code> and <code>BEG</code> align vertically likeso:</p><pre><code class="language-plaintext">-----END CERTIFICATE----------BEGIN CERTIFICATE-----</code></pre>]]></content>
    </entry><entry>
       <title><![CDATA[This is how our workspace looks like]]></title>
       <author><name>Rishi Mohan</name></author>
      <link href="https://www.bigbinary.com/blog/this-is-how-our-workspace-looks-like"/>
      <updated>2019-09-26T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/this-is-how-our-workspace-looks-like</id>
      <content type="html"><![CDATA[<p>BigBinary has been remote and flexible since the start, and it's one of the bestthings a company can offer. You don't need to spend hours commuting, you canwork when you feel productive. Working remotely also means that you have theflexibility of working from Starbucks, from a library or from your home. You canset up your own workspace at home and still have the office-like feeling.</p><p>We recently got a chance to see workspaces of our colleagues and everyone sharedphotos of environments they work in on Slack and it was fun seeing everyone'sdesk and the setup they have. We thought it would be fun to share a peek at thehome offices we have. Here we go.</p><h2>Akhil Gautam</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpeg" alt="BigBinary Remote Workspace"></p><h2>Amit Choudhary</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Chimed Palden</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Chirag Shah</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_1.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_2.jpg" alt="BigBinary Remote Workspace"></p><h2>Ershad Kunnakkadan</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpeg" alt="BigBinary Remote Workspace"></p><h2>Mohit Natoo</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Navaneeth PK</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_1.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_2.png" alt="BigBinary Remote Workspace"></p><h2>Neeraj Singh</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_1.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_2.jpg" alt="BigBinary Remote Workspace"></p><h2>Nitin Kalasannavar</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Paras Bansal</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Pranav Raj</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Prathamesh Sonpatki</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Rahul Mahale</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Rishi Mohan</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_1.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_2.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_3.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_4.jpg" alt="BigBinary Remote Workspace"><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup_5.webp" alt="BigBinary Remote Workspace"></p><h2>Shibin Madassery</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Sony Mathew</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Sunil Kumar</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Tyler and Naiara</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Unnikrishnan KP</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p><h2>Vishal Telangre</h2><p><img src="/blog/images/images_used_in_blog/2019/this-is-how-our-workspace-looks-like/setup.jpg" alt="BigBinary Remote Workspace"></p>]]></content>
    </entry><entry>
       <title><![CDATA[Marketing strategy at BigBinary]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/marketing-strategy-at-bigbinary"/>
      <updated>2019-03-18T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/marketing-strategy-at-bigbinary</id>
      <content type="html"><![CDATA[<p>BigBinary started in 2011. Here are our revenue numbers for the last 7 years.</p><p><img src="/blog/images/images_used_in_blog/2019/marketing-strategy-at-bigbinary/revenue.png" alt="BigBinary revenue"></p><p>We achieved this to date without having any outbound marketing and salesstrategy.</p><ul><li>We have never sent a cold email.</li><li>We have never sent a cold LinkedIn message.</li><li>The only time we advertised was a period of two months when we tried Googleadvertisements, with no outcomes.</li><li>We do not sponsor any podcast.</li><li>We have not had a sales person.</li><li>We have not had a marketing person.</li></ul><p>We have kept our head down and have focused on what we do best, such asdesigning, developing, debugging, devops, and blogging.</p><p>This is what has worked out for us so far:</p><ul><li>We contribute to the community through<a href="https://blog.bigbinary.com">blog posts</a> and open source.</li><li>We sponsor community events like Rails Girls and Ruby Conf India.</li><li>We sponsor many React and Ruby meetups.</li><li>We focus on keeping our existing clients happy.</li></ul><p>Over the years I have come across many people who aspire to be freelancers.While it is not for everyone, I encourage them to give freelancing a try.</p><p>The greatest hindrance I have seen is that they stress over sales and marketing,and as it should be. Being a freelancer means constant need to find your nextclient.</p><p>I'm not here to say what others ought to do. I'm here to say what has worked outfor BigBinary over the last 7 years.</p><p>While we plan to experiment with new forms of marketing, networking, and saleschannel as we grow, it is not the end-all-be-all for freelancers. Whilemarketing, networking, and sales may be effective for some, it was not how westarted BigBinary and may not be how you want to start as well.</p><p>For us at BigBinary, it has been writing blogs. When we come across apotentially intriguing blog topic, we save the topic by creating a Github issue.When we have downtime, we pick up a topic from our issues list. Its as simpleas that and has been our primary driver of growth thus far.</p><p>While you should experiment to find out what works best for you, you need tofind out what suits your personality. If you are good at teaching throughvideos, consider creating your own YouTube channel. If you contribute to opensource, try creating a blog about your efforts and learnings. If you are good atconcentrating on a niche technology, build your marketing and business aroundthat.</p><p>I can confidently say that majority of people I met and who want to befreelancer would do fine if they simply share what they are learning. Most ofthese people do technical work. Some of them already blog and others can blog. Ablog is a decent start nearly everybody will say. I'm saying that it is a goodend too.</p><p>If you do not want to do any other form of marketing then that's fine too. Justblogging will work out fine for you just like it has worked out fine for us atBigBinary.</p><p>Just because you are going to be a freelancer you dont have to change who youare. If you don't like sending cold emails then don't. If you do not likenetworking then thats alright as well. Write personal emails, dump corporatetalk, show compassion and be genuine.</p><p>So go on and do some freelancing. It would teach you a lot about softwaredevelopment, business, life, managing money, creating value and capturing value.It will be rough at times. And it would be hard at times. But it would also be aton of fun.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Resolve foreign key constraint conflict]]></title>
       <author><name>Narendra Rajput</name></author>
      <link href="https://www.bigbinary.com/blog/resolve-foreign-key-constraint-conflict-while-copying-data-using-topological-sort"/>
      <updated>2019-02-05T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/resolve-foreign-key-constraint-conflict-while-copying-data-using-topological-sort</id>
      <content type="html"><![CDATA[<p>We have a client that uses multi-tenant databasewhere each database holds data for each of their customers.Whenever a new customer is added, a service dynamically creates a new database.In order to seed this new database we were taskedto implement a feature to copy data from existing &quot;demo&quot; database.</p><p>The &quot;demo&quot; database is actually a live client where sales team does demo.This ensures that the data that is copied is fresh and not stale.</p><p>We implemented a solution where we simply listed all the tables in namespace and used <a href="https://github.com/zdennis/activerecord-import">activerecord-import</a>to copy the table data.We used <code>activerecord-import</code> gem to keep code agnostic of underlying database as we used different databases in development from production.Production is &quot;SQL Server&quot; and development database is &quot;PostgreSQL&quot;.Why this project ended up having different database in development and in productionis worthy of a separate blog.</p><p>When we started using the above mentioned strategy thenwe quickly ran into a problem.Inserts for some tables were failing.</p><pre><code class="language-plaintext">insert or update on table &quot;dependent_table&quot; violates foreign key constraint &quot;fk_rails&quot;Detail: Key (column)=(1) is not present in table &quot;main_table&quot;.</code></pre><p>The issue was we had foreign key constraints on some tables and &quot;dependent&quot; table was being processed before the &quot;main&quot; table.</p><p>So initially we thought of simply hard coding the sequence in which to process the tables. It means if any new table is added then we will have to update the service to include the newly added table. So we needed a way to identify the foreign key dependencies and determine the sequence to copy the tables at runtime. To resolve this issue, we thought of using<a href="https://en.wikipedia.org/wiki/Topological_sorting">Topological Sorting</a>.</p><h2>Topological Sorting</h2><p>To get started we need the list of dependencies of &quot;main&quot; and &quot;dependent&quot; tables.In Postgresql, this sql query fetches the table dependencies.</p><pre><code class="language-sql">SELECT    tc.table_name AS dependent_table,    ccu.table_name AS main_tableFROM    information_schema.table_constraints AS tc    JOIN information_schema.key_column_usage AS kcu      ON tc.constraint_name = kcu.constraint_name      AND tc.table_schema = kcu.table_schema    JOIN information_schema.constraint_column_usage AS ccu      ON ccu.constraint_name = tc.constraint_name      AND ccu.table_schema = tc.table_schemaWHERE constraint_type = 'FOREIGN KEY'and (tc.table_name like 'namespace_%' or ccu.table_name like 'namespace_%');=&gt; dependent_table  | main_table-----------------------------------   dependent_table1 | main_table1   dependent_table2 | main_table2</code></pre><p>The above query fetches all the dependencies for only the tables have namespace or the tables we are interested in.The output of above query was <code>[[dependent_table1, main_table1], [dependent_table2, main_table2]]</code>.</p><p>Ruby has a <code>TSort</code> module that for implementing topological sorts.So we needed to run the topological sort on the dependencies. So we inserted the dependencies into a hash and included the <code>TSort</code> functionality into the hash. Following is the way to include the <code>TSort</code> module into hash by subclassing the <code>Hash</code>.</p><pre><code class="language-ruby">require &quot;tsort&quot;class TsortableHash &lt; Hash  include TSort  alias tsort_each_node each_key  def tsort_each_child(node, &amp;block)    fetch(node).each(&amp;block)  endend# Borrowed from https://www.viget.com/articles/dependency-sorting-in-ruby-with-tsort/</code></pre><p>Then we simply added all the tables to dependency hash, as below</p><pre><code class="language-ruby">tables_to_sort = [&quot;dependent_table1&quot;, &quot;dependent_table2&quot;, &quot;main_table1&quot;]dependency_graph = tables_to_sort.inject(TsortableHash.new) {|hash, table| hash[table] = []; hash }table_dependency_map = fetch_table_dependencies_from_database=&gt; [[&quot;dependent_table1&quot;, &quot;main_table1&quot;], [&quot;dependent_table2&quot;, &quot;main_table2&quot;]]# Add missing tables to dependency graphtable_dependency_map.flatten.each {|table| dependency_graph[table] ||= [] }table_dependency_map.each {|constraint| dependency_graph[constraint[0]] &lt;&lt; constraint[1] }dependency_graph.tsort=&gt; [&quot;main_table1&quot;, &quot;dependent_table1&quot;, &quot;main_table2&quot;, &quot;dependent_table2&quot;]</code></pre><p>The output above, is the dependency resolved sequence of tables.</p><p>Topological sorting is pretty useful in situations where we need to resolve dependencies and Ruby provides a really helpful tool <code>TSort</code> to implement it without going into implementation details. Although I did spend time in understanding the underlying algorithm for fun.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Inline Installation of Firefox Extension]]></title>
       <author><name>Chirag Shah</name></author>
      <link href="https://www.bigbinary.com/blog/inline-installation-of-firefox-extension"/>
      <updated>2018-09-27T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/inline-installation-of-firefox-extension</id>
      <content type="html"><![CDATA[<h2>Inline Installation</h2><p>Firefox extensions,similar to Chrome extensions,help us modifyandpersonalize our browsing experienceby adding new featuresto the existing sites.</p><p>Once we've publishedour extension to the<a href="https://addons.mozilla.org/">Mozilla's Add-on store(AMO)</a>,users who browse the AMOcan find the extensionand install it with one-click.But,if a user is already on our sitewhere a link is providedto the extension's AMO listing page,they would need tonavigate away from ourwebsite to the AMO,complete the install process,and then return back to our site.That is a bad user experience.</p><p>The inline installation enables us toinitiate the extension installationfrom our site.The extension can still be hosted on the AMObut userswould no longer have toleave our site to install it.</p><p>We had to try out a few suggested approachesbefore we got it working.</p><h2>InstallTrigger</h2><p><code>InstallTrigger</code> (Link is not available)is an interfaceincluded in the Mozilla's Apps APIfor installing extensions.Using JavaScript,the <code>install</code> methodof <code>InstallTrigger</code> can be usedto start the download and installationof an extension (or anything packaged in a .xpi file)from a Web page.</p><p>A XPI(pronounced as &quot;zippy&quot;)is similar to a zip file,which contains manifest file andthe install script for the extension.</p><p>So, let's try to install theGrammarly Extension for Firefox.To use it,we first need its .xpi file's location.Once we have published our extension on the AMO,we can navigate to the listings pagefor it and get the link for the .xpi.</p><p>For our present example,here's the listing page for<a href="https://addons.mozilla.org/en-US/firefox/addon/grammarly-1/">Grammarly Extension</a>.</p><p>Here,we can get the .xpi file's locationby right clicking on the<code>+ Add to Firefox</code> buttonand clicking on <code>Copy Link Location</code>.Note that the <code>+ Add to Firefox</code> buttonwould only be visibleif we browse the link on a Firefox browser.Otherwise, it would be replaced bya <code>Get Firefox Now</code> button.</p><p>Once we have the URL,we can trigger the installation via JavaScripton our web page.</p><pre><code class="language-javascript">InstallTrigger.install({  &quot;Name of the Extension&quot;: {    URL: &quot;url pointing to the .xpi file's location on AMO&quot;,  },});</code></pre><h2>Pointing to the latest version of the Extension</h2><p>When we used the URL in the above code,the .xpi file's URLwas specific to the extension's current version.If the extension has an update,the installed extensions for existing userswould be updated automatically.But the URL to the .xpi on our websitewould be pointing to the older version.Although the old link would still work,we would always want new users to downloadthe latest version.</p><p>To do that,we can either fetch the listing pagein the background and parse the HTMLto get the latest link.But that approach can break if the HTML changes.</p><p>Or we can query the Addons Services API,which returns the information for the extension in XML format.</p><p>For the Grammarly Extension, we first need its slug-id.We can get it by looking at its listing page's URL.From <code>https://addons.mozilla.org/en-US/firefox/addon/grammarly-1/</code>,we can note down the slug which is <code>grammarly-1</code></p><p>Using this slug id, we can now get the extension details using<code>https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/grammarly-1</code>.It returns the info for the Grammarly Extension.What we are particularly interested in is the value in the <code>&lt;install&gt;</code> node.That is what the desired value is for the latest version for our .xpi.</p><p>Let's see how we can implement the whole thing using React.</p><pre><code class="language-javascript">import axios from &quot;axios&quot;;import cheerio from &quot;cheerio&quot;;const FALLBACK_GRAMMARLY_EXTENSION_URL =  &quot;https://addons.mozilla.org/firefox/downloads/file/1027073/grammarly_for_firefox-8.828.1757-an+fx.xpi&quot;;const URL_FOR_FETCHING_XPI = `https://services.addons.mozilla.org/en-US/firefox/api/1.5/addon/grammarly-1`;export default class InstallExtension extends Component {  state = {    grammarlyExtensionUrl: FALLBACK_GRAMMARLY_EXTENSION_URL,  };  componentWillMount() {    axios.get(URL_FOR_FETCHING_XPI).then((response) =&gt; {      const xml = response.data;      const $ = cheerio.load(xml);      const grammarlyExtensionUrl = $(&quot;addon install&quot;).text();      this.setState({ grammarlyExtensionUrl });    });  }  triggerInlineInstallation = (event) =&gt; {    InstallTrigger.install({      Grammarly: { URL: this.state.grammarlyExtensionUrl },    });  };  render() {    return (      &lt;Button onClick={this.triggerInlineInstallation}&gt;        Install Grammarly Extension      &lt;/Button&gt;    );  }}</code></pre><p>In the above code,we are using the npm packages<a href="https://github.com/axios/axios">axios</a>for fetching the xml and<a href="https://github.com/cheeriojs/cheerio">cheerio</a>for parsing the xml.Also, we have set a fallback URL as the initial valuein case the fetching of the new URLfrom the xml response fails.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Debug failing puppeteer tests due to background tab]]></title>
       <author><name>Rohit Kumar</name></author>
      <link href="https://www.bigbinary.com/blog/debugging-failing-tests-in-background-tab-in-puppeteer"/>
      <updated>2018-08-15T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/debugging-failing-tests-in-background-tab-in-puppeteer</id>
      <content type="html"><![CDATA[<p>We have been using puppeteer in one of our projects to write end-to-end tests.We run our tests in headful mode to see the browser in action.</p><p>If we start puppeteer tests and do nothing in our laptop (just watch the testsbeing executed) then all the tests will pass.</p><p>However if we are doing our regular work in our laptop while tests are runningthen tests would fail randomly. This was quite puzzling.</p><p>Debugging such flaky tests is hard. We first suspected that the test casesthemselves needed more of implicit waits for element/text to be present/visibleon the DOM.</p><p>After some debugging using puppeteer protocol logs, it seemed like the browserwas performing certain actions very slowly or was waiting for the browser to beactive ( in view ) before performing those actions.</p><p>Chrome starting with version 57 introduced<a href="https://developers.google.com/web/updates/2017/03/background_tabs">throttling of background tabs</a>for improving performance and battery life. We execute one test per browsermeaning we didn't make use of multiple tabs. Also tests failed only when theuser was performing some other activities while the tests were executing inother background windows.<a href="https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API">Pages were hidden</a>only when user switched tabs or minimized the browser window containing the tab.</p><p>After observing closely we noticed that the pages were making requests to theserver. The issue was the page was not painting if the page is not in view. Weadded flag <code>--disable-background-timer-throttling</code> but we did not notice anydifference.</p><p>After doing some searches we noticed the flag <code>--disable-renderer-backgrounding</code>was being used in<a href="https://github.com/karma-runner/karma-chrome-launcher/blob/01c7efc870e64733d81347d4996fb9bcbf099825/index.js#L42-L46">karma-launcher</a>.The comment states that it is specifically required on macOS. Here is the<a href="https://cs.chromium.org/chromium/src/content/browser/renderer_host/render_widget_host_impl.cc?l=684-689">code</a>responsible for lowering the priority of the renderer when it is hidden.</p><p>But the new flag didn't help either.</p><p>While looking at all the available command line switches for chromium, westumbled upon <code>--disable-backgrounding-occluded-windows</code>. Chromium alsobackgrounds the renderer while the window is not visible to the user. It seemsfrom the comment that the flag<a href="https://cs.chromium.org/chromium/src/content/public/common/content_switches.cc?l=99-102">kDisableBackgroundingOccludedWindowsForTesting</a>is specifically added to avoid non-deterministic behavior during tests.</p><p>We have added following flags to chromium for running our integration suite andthis solved our problem.</p><pre><code class="language-js">const chromeArgs = [  &quot;--disable-background-timer-throttling&quot;,  &quot;--disable-backgrounding-occluded-windows&quot;,  &quot;--disable-renderer-backgrounding&quot;,];</code></pre><p>References</p><ul><li><a href="https://docs.google.com/document/d/18_sX-KGRaHcV3xe5Xk_l6NNwXoxm-23IOepgMx4OlE4/pub">Background tabs &amp; offscreen frames </a></li><li><a href="https://www.chromium.org/developers/design-documents/mac-occlusion">Mac Window Occlusion API Use</a></li></ul>]]></content>
    </entry><entry>
       <title><![CDATA[Auto-format Elm code with elm-format before commit]]></title>
       <author><name>Ritesh Pillai</name></author>
      <link href="https://www.bigbinary.com/blog/format-your-elm-code-with-elm-format-before-committing"/>
      <updated>2018-07-09T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/format-your-elm-code-with-elm-format-before-committing</id>
      <content type="html"><![CDATA[<p>In one of our earlier posts<a href="https://blog.bigbinary.com/2017/06/12/using-prettier-and-rubocop-in-ruby-on-rails-to-format-javascript-css-ruby-files.html">we talked about</a>how we set up <a href="https://github.com/prettier/prettier">prettier</a> and<a href="https://github.com/bbatsov/rubocop">rubocop</a> to automatically format ourJavaScript and Ruby code on git commit.</p><p>Recently we started working with Elm in a couple of our projects -<a href="https://github.com/bigbinary/apisnapshot">APISnapshot</a> and<a href="https://github.com/bigbinary/acehelp">AceHelp</a>.</p><p>Tools like prettier and rubocop have really helped us take a load off our mindwith regards to formatting code. And one of the very first things we wanted tosort out when we started doing Elm was pretty printing our Elm code.</p><p><a href="https://github.com/avh4/elm-format">elm-format</a> created by<a href="https://github.com/avh4">Aaron VonderHaar</a> formats Elm source code according toa standard set of rules based on the official<a href="http://elm-lang.org/docs/style-guide">Elm Style Guide</a>.</p><h2>Automatic code formatting</h2><p>Let's setup git hook to automatically take care of code formatting. We canachieve this much like how we did it in our previous<a href="https://blog.bigbinary.com/2017/06/12/using-prettier-and-rubocop-in-ruby-on-rails-to-format-javascript-css-ruby-files.html">post</a>,using <a href="https://github.com/typicode/husky/tree/v0.14.3">Husky</a> and<a href="https://github.com/okonet/lint-staged">Lint-staged</a>.</p><p>Let's add Husky and lint-staged as dev dependencies to our project. And forcompleteness also include elm-format as a dev dependency.</p><pre><code class="language-javascript">npm install --save-dev husky lint-staged elm-format</code></pre><p>Husky makes it real easy to create git hooks. Git hooks are scripts that areexecuted by git before or after an event. We will be using the <code>pre-commit</code> hookwhich is run after you do a <code>git commit</code> command but before you type in a commitmessage.</p><p>This way we can change and format files that's about to be committed by runningelm-format using Husky.</p><p>But there is one problem here. The changed files do not get added back to ourcommit.</p><p>This is where Lint-staged comes in. Lint-staged is built to run linters onstaged files. So instead of running elm-format on a pre-commit hook we would runlint-staged. And we can configure lint-staged such that elm-format is run on allstaged elm files.</p><p>We can also include Prettier to take care of all staged JavaScript files too.</p><p>Lets do this by editing our <code>package.json</code> file.</p><pre><code class="language-json">{  &quot;scripts&quot;: {    &quot;precommit&quot;: &quot;lint-staged&quot;  },  &quot;lint-staged&quot;: {    &quot;*.elm&quot;: [&quot;elm-format --yes&quot;, &quot;git add&quot;],    &quot;*.js&quot;: [&quot;prettier --write&quot;, &quot;git add&quot;]  }}</code></pre><p>All set and done!</p><p>Now whenever we do a <code>git commit</code> command, all our staged elm and JavaScriptfiles will get properly formatted before the commit goes in.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Continuous release of a chrome extension using CircleCI]]></title>
       <author><name>Amit Choudhary</name></author>
      <link href="https://www.bigbinary.com/blog/continuously-upload-chrome-extension-with-circleci"/>
      <updated>2018-06-27T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/continuously-upload-chrome-extension-with-circleci</id>
      <content type="html"><![CDATA[<p>We have recently worked on many chrome extensions. Releasing new chromeextensions manually gets tiring after a while.</p><p>So, we thought about automating it with CircleCI, similar to continuousdeployment.</p><p>We are using the following configuration in <code>circle.yml</code> to continuously releasechrome extensions from the master branch.</p><pre><code class="language-yaml">workflows:  version: 2  main:    jobs:      - test:          filters:            branches:              ignore: []      - build:          requires:            - test          filters:            branches:              only: master      - publish:          requires:            - build          filters:            branches:              only: masterversion: 2jobs:  test:    docker:      - image: cibuilds/base:latest    steps:      - checkout      - run:          name: &quot;Install Dependencies&quot;          command: |            apk add --no-cache yarn            yarn      - run:          name: &quot;Run Tests&quot;          command: |            yarn run test  build:    docker:      - image: cibuilds/chrome-extension:latest    steps:      - checkout      - run:          name: &quot;Install Dependencies&quot;          command: |            apk add --no-cache yarn            apk add --no-cache zip            yarn      - run:          name: &quot;Package Extension&quot;          command: |            yarn run build            zip -r build.zip build      - persist_to_workspace:          root: /root/project          paths:            - build.zip  publish:    docker:      - image: cibuilds/chrome-extension:latest    environment:      - APP_ID: &lt;APP_ID&gt;    steps:      - attach_workspace:          at: /root/workspace      - run:          name: &quot;Publish to the Google Chrome Store&quot;          command: |            ACCESS_TOKEN=$(curl &quot;https://accounts.google.com/o/oauth2/token&quot; -d &quot;client_id=${CLIENT_ID}&amp;client_secret=${CLIENT_SECRET}&amp;refresh_token=${REFRESH_TOKEN}&amp;grant_type=refresh_token&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&quot; | jq -r .access_token)            curl -H &quot;Authorization: Bearer ${ACCESS_TOKEN}&quot; -H &quot;x-goog-api-version: 2&quot; -X PUT -T /root/workspace/build.zip -v &quot;https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}&quot;            curl -H &quot;Authorization: Bearer ${ACCESS_TOKEN}&quot; -H &quot;x-goog-api-version: 2&quot; -H &quot;Content-Length: 0&quot; -X POST -v &quot;https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish&quot;</code></pre><p>We have created three jobs named as <code>test</code>, <code>build</code> and <code>publish</code> and used thesejobs in our workflow to run tests, build the extension, and publish them to thechrome store, respectively. Every step requires the previous step to runsuccessfully.</p><p>Let's check each job one by one.</p><pre><code class="language-yaml">test:  docker:    - image: cibuilds/base:latest  steps:    - checkout    - run:        name: &quot;Install Dependencies&quot;        command: |          apk add --no-cache yarn          yarn    - run:        name: &quot;Run Tests&quot;        command: |          yarn run test</code></pre><p>We use <a href="https://github.com/cibuilds/base">cibuilds</a> docker image for this job.First, we do a checkout to the branch and then use <code>yarn</code> to installdependencies. Alternatively, we can use <code>npm</code> to install dependencies as well.Then, as the last step, we are use <code>yarn run test</code> to run tests. We can skipthis step if running tests is not needed.</p><pre><code class="language-yaml">build:  docker:    - image: cibuilds/chrome-extension:latest  steps:    - checkout    - run:        name: &quot;Install Dependencies&quot;        command: |          apk add --no-cache yarn          apk add --no-cache zip          yarn    - run:        name: &quot;Package Extension&quot;        command: |          yarn run build          zip -r build.zip build    - persist_to_workspace:        root: /root/project        paths:          - build.zip</code></pre><p>For building chrome extensions, we use the<a href="https://github.com/cibuilds/chrome-extension">chrome-extension</a> image. Here, wealso first do a checkout and then, install dependencies using yarn. Note, we areinstall zip utility along with yarn because we need to zip our chrome extensionbefore publishing it in next step. Also, we are not generating version numberson our own. The version number will be picked from the manifest file. This stepassumes that we have a task named <code>build</code> in <code>package.json</code> to build our app.</p><p>The Chrome store rejects multiple uploads with the same version number. So, wehave to make sure to update the version number, which should be unique in themanifest file before this step.</p><p>In the last step, we use <code>persist_to_workspace</code> to make <code>build.zip</code> availablefor the next step, publishing.</p><pre><code class="language-yaml">publish:  docker:    - image: cibuilds/chrome-extension:latest  environment:    - APP_ID: &lt;APP_ID&gt;  steps:    - attach_workspace:        at: /root/workspace    - run:        name: &quot;Publish to the Google Chrome Store&quot;        command: |          ACCESS_TOKEN=$(curl &quot;https://accounts.google.com/o/oauth2/token&quot; -d &quot;client_id=${CLIENT_ID}&amp;client_secret=${CLIENT_SECRET}&amp;refresh_token=${REFRESH_TOKEN}&amp;grant_type=refresh_token&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&quot; | jq -r .access_token)          curl -H &quot;Authorization: Bearer ${ACCESS_TOKEN}&quot; -H &quot;x-goog-api-version: 2&quot; -X PUT -T /root/workspace/build.zip -v &quot;https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}&quot;          curl -H &quot;Authorization: Bearer ${ACCESS_TOKEN}&quot; -H &quot;x-goog-api-version: 2&quot; -H &quot;Content-Length: 0&quot; -X POST -v &quot;https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish&quot;</code></pre><p>For publishing of the chrome extension, we use the<a href="https://github.com/cibuilds/chrome-extension">chrome-extension</a> image.</p><p>We need <code>APP_ID</code>, <code>CLIENT_ID</code>, <code>CLIENT_SECRET</code> and<code>REFRESH_TOKEN</code>/<code>ACCESS_TOKEN</code> to publish our app to the chrome store.</p><p><code>APP_ID</code> needs to be fetched from<a href="https://chrome.google.com/webstore/developer/dashboard">Google Webstore Developer Dashboard</a>.<code>APP_ID</code> is unique for each app whereas <code>CLIENT_ID</code>, <code>CLIENT_SECRET</code> and<code>REFRESH_TOKEN</code>/<code>ACCESS_TOKEN</code> can be used for multiple apps. Since <code>APP_ID</code> isgenerally public, we specify that in the yml file. <code>CLIENT_ID</code>, <code>CLIENT_SECRET</code>and <code>REFRESH_TOKEN</code>/<code>ACCESS_TOKEN</code> are stored as private environment variablesusing CircleCI UI. For cases when our app is unlisted in the chrome store, weneed to store <code>APP_ID</code> as a private environment variable.</p><p><code>CLIENT_ID</code> and <code>CLIENT_SECRET</code> need to be fetched from<a href="https://console.developers.google.com/">Google API console</a>. There, we need toselect a project and then click on the credentials tab. If there is no project,we need to create one and then access the credentials tab.</p><p><code>REFRESH_TOKEN</code> needs to be fetched from Google API. It also defines the scopeof access for Google APIs. We need to refer to<a href="https://developers.google.com/identity/protocols/OAuth2WebServer">Google OAuth2</a>for obtaining the refresh token. We can use any language library.</p><p>In the first step of the <code>publish</code> job, we are attaching a workspace to access<code>build.zip</code>, which was created previously. Now, by using all the required tokensobtained previously, we need to obtain an access token from Google OAuth API(Link is not available), which must be used to push the app to the chrome store.Then, we make a <code>PUT</code> request to the Chrome store API to push the app, and thenuse the same API again, to publish the app.</p><p>Uploading via API has one more advantage over manual upload. Manual uploadgenerally takes up to 1 hour to show the app in the chrome store. Whereasuploading using Google API generally reflects the app within 5-10 minutes,considering app does not go for a review by Google.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Our Thoughts on iOS 12]]></title>
       <author><name>Rishi Mohan</name></author>
      <link href="https://www.bigbinary.com/blog/ios-12"/>
      <updated>2018-06-11T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/ios-12</id>
      <content type="html"><![CDATA[<p><img src="/blog/images/images_used_in_blog/2018/ios-12/ios-12-lock-home-screen.png" alt="iOS 12 on iPhone 8 Red"></p><p>Apple announced iOS 12 at WWDC 2018 a few days back. Being honest, it was a bitdisappointing to see some of the most requested features not being there iniOS 12. Like users have been asking for Dark mode since before iOS 11, and theability to set default apps. Its more of an update focussed on performanceimprovements, usability, and compatibility. The fact that iOS 12 is alsoavailable for the iPhones Apple released 5 years back is a great effort fromApple to keep users happy. And unlike last couple of years, this time we decidedto calm our curiosities and installed iOS 12 beta 1 on our devices right awayafter Apple released it for developers. This blog is based on our experiencewith iOS 12 on iPhone 8 Plus.</p><h2>Installing iOS 12 on your iPhone</h2><p>First things first, make sure you have iPhone 5s or newer. And before gettingstarted, plug-in your phone to iTunes and take a full-backup in case your phonegets bricked while installing iOS 12, which is very unlikely.</p><p>Once done, download and install<a href="https://beta.thuthuatios.com/en/">beta profile</a>{:target=&quot;_blank&quot;} for iOS 12and then download and update from Software Update section just like youinstall a regular iOS update. Its a straightforward OTA update process whichyoure already familiar with.</p><p><strong>Note: This beta profile is from a third-party developer and is not officiallyfrom Apple. Apple will officially release public beta in around a month.</strong></p><p>Weve been running iOS 12 since last week now and here are our thoughts on theadditions and changes introduced in iOS 12.</p><h3>iOS 12 is fast</h3><p>The performance improvements are significant and definitely noticeable.Previously on iOS 11, while accessing spotlight by swiping down from homescreen, it used to lag. And not just that, sometimes keyboard didnt even usedto show up and we had to repeat the same action to make it work. But things arefaster and better in iOS 12. The keyboard gets up as soon as spotlight shows up.</p><p>Another thing that weve noticed is the multitasking shortcut for switching tothe last app by 3d touching on left wasn't that reliable in iOS 11, so much thatit was easy to ignore the feature altogether than to use it, but in iOS 12 thesame shortcut is very smooth. Although there are times when it still doesntwork well, but that's very rare.</p><p>Spotlight widgets load faster in iOS 12 than they used to in iOS 11. Apart fromthis, 3d touch feels pretty smooth too. Its good to see Apple squeezing out thepower to improve the already good performance in iOS.</p><h3>Notifications</h3><p>&lt;section style=&quot;float: right; max-width: 280px; margin-left: 30px;&quot;&gt;&lt;img src=&quot;/blog/images/images_used_in_blog/2018/ios-12/ios-12-notifications.png&quot; alt=&quot;Notifications in iOS 12&quot;&gt;&lt;/section&gt;</p><p>Notifications in iOS 11 are a mess, theres no grouping, theres no way toquickly control notifications, theres no way to set priorities. Apple has addednotifications grouping and better notification management in iOS 12, so nownotifications from the same app are grouped together and you can control howoften you want to get notifications from the apps right from the notification.</p><p>We think the implementation can be a whole lot better. For the notificationsthat are grouped, you get to see only the last notification from that app, abetter way wouldve been to show two or three notifications and cut the rest.Theres no Notification pinning or snoozing which could've been very usefulfeatures.</p><h3>Screen time and App limits</h3><p>Theres a new feature in iOS 12 called Screen Time which is more like bird's-eyeview of your phone usage. Theres a saying that you cant improve something thatyou cant measure. Screen Time is a feature thats going to be very useful foreveryone who wants to cut down time on Social apps or overall phone usage. Itshows you every little detail of how many apps you use and for how much time andat what times. Not only this, it also keeps track of how many times you pickedup your phone, and how many notifications you receive from the apps you have onyour phone.</p><p><img src="/blog/images/images_used_in_blog/2018/ios-12/ios-12-screen-time.png" alt="Screen Time in iOS 12"></p><p>Other useful sub-feature of Screen time is App limits, which allows you to setlimit on app usage based on app or category. So lets say you dont want to useWhatsApp for more than 30 mins a day, you can do that through App limits. Itworks for app categories including Games, Social Networking, Entertainment,Creativity, Productivity, Education, Reading &amp; Reference, Health &amp; Fitness. Soyou can limit it via category which works across apps. Plus, it syncs acrossyour other iOS devices, so you cant cheat that way.</p><h3>Siri and Shortcuts app</h3><p>&lt;section style=&quot;float: right; max-width: 250px; margin-left: 30px;&quot;&gt;&lt;img src='/blog/images/images_used_in_blog/2018/ios-12/ios-12-siri-shortcuts.png' alt='Siri Shortcuts in iOS 12'&gt;&lt;/section&gt;</p><p>In iOS 12, you can assign custom shortcuts to Siri to trigger specific actions,which not only works for System apps but also with third-party apps. So now ifyou want to send a specific message to someone on WhatsApp, you can assign acommand for that to Siri and you can trigger that action just from Siri usingthat command.</p><p>Apple has also introduced a new app in iOS 12 called Shortcuts. Shortcuts applets you group actions and run those actions quickly. Although Shortcuts appisnt there in iOS 12 beta 1 but we think its one of best addition in iOS 12.</p><h3>Updated Photos app</h3><p>Photos app now has new section called &quot;For you&quot;, where it shows all the newAlbums, your best moments, share suggestions, photos and effect suggestions.This is more like the Assistant tab of Google Photos app. Also you can shareselected photos or albums with your friends right from the Photos app.</p><p>The Album tab in Photos app is redesigned for easier navigation. Also there's anew tab for Search which has been advanced, so you can now search photos usingtags like &quot;surfing&quot; and &quot;vacation&quot;.</p><p>It's good to see Apple paying attention to up the Photos app but we still thinkGoogle Photos is a better option for average person considering it lets youstore photos in Cloud for free. Also photo organization in Google Photos is muchbetter than in new Photos app in iOS 12.</p><h3>Enhanced Do Not Disturb Mode</h3><p>Do Not Disturb in iOS 12 is enhanced to be more flexible. You can now enable DoNot Disturb mode to end automatically in an hour, or at night, or according toyour Calendar events, or even based on your location.</p><p>Not just that, Do Not Disturb has a new <em>Bedtime mode</em> enabling which will shutall your notifications during bed time and dim your display. And when you willwake up, it'll show you a welcome back message along with weather details on thelock screen.</p><h3>Conclusion</h3><p>There are other updates and under the hood improvements as well, like <em>newMeasure app, redesigned iBooks app, tracking prevention, group FaceTime</em> etc.Overall, we think its an okay update considering there are not as many bugs asthere should be according to Apple standards. The force touch in the keyboard todrag cursor doesnt work, Skype and some other apps crash, but for the mostpart, its good enough to be installed on your primary device.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Modelling state in Elm to reflect business logic]]></title>
       <author><name>Ritesh Pillai</name></author>
      <link href="https://www.bigbinary.com/blog/modelling-state-in-elm-to-reflect-business-logic"/>
      <updated>2018-06-04T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/modelling-state-in-elm-to-reflect-business-logic</id>
      <content type="html"><![CDATA[<p>We recently made<a href="https://blog.bigbinary.com/2018/05/25/apisnapshot-built-using-elm-and-ruby-on-rails-is-open-source.html">ApiSnapshot open source</a>.As mentioned in that blog we ported code from React.js to Elm.</p><p>One of the features of <code>ApiSnapshot</code> is support for <code>Basic Authentication</code>.</p><p><img src="/blog/images/images_used_in_blog/2018/modelling-state-in-elm-to-reflect-business-logic/apisnapshot-with-basic-authentication.png" alt="ApiSnapshot with basic authentication"></p><p>While we were rebuilding the whole application in Elm, we had to port the &quot;AddBasic Authentication&quot; feature. This feature can be accessed from the &quot;More&quot;drop-down on the right-hand side of the app and it lets user add username andpassword to the request.</p><p>Let's see how the <code>Model</code> of our Elm app looks.</p><pre><code class="language-elm">type alias Model ={ request : Request.MainRequest.Model, response : Response.MainResponse.Model, route : Route}</code></pre><p>Here is the Model in <em>Request.MainRequest</em> module.</p><pre><code class="language-elm">type alias APISnapshotRequest ={ url : String, httpMethod : HttpMethod, requestParameters : RequestParameters, requestHeaders : RequestHeaders, username : Maybe String, password : Maybe String, requestBody : Maybe RequestBody}type alias Model ={ request : APISnapshotRequest, showErrors : Bool}</code></pre><p><code>username</code> and <code>password</code> fields are optional for the users so we kept them as<code>Maybe</code> types.</p><p>Note that API always responds with <code>username</code> and <code>password</code> whether userclicked to add <code>Basic Authentication</code> or not. The API would respond with a<strong><em>null</em></strong> for both username and password when a user tries to retrieve asnapshot for which user did not fill <code>username</code> and <code>password</code>.</p><p>Here is a sample API response.</p><pre><code class="language-json">{  &quot;url&quot;: &quot;http://dog.ceo/api/breed/affenpinscher/images/random&quot;,  &quot;httpMethod&quot;: &quot;GET&quot;,  &quot;requestParams&quot;: {},  &quot;requestHeaders&quot;: {},  &quot;requestBody&quot;: null,  &quot;username&quot;: &quot;alanturning&quot;,  &quot;password&quot;: &quot;welcome&quot;,  &quot;assertions&quot;: [],  &quot;response&quot;: {    &quot;response_headers&quot;: {      &quot;age&quot;: &quot;0&quot;,      &quot;via&quot;: &quot;1.1 varnish (Varnish/6.0), 1.1 varnish (Varnish/6.0)&quot;,      &quot;date&quot;: &quot;Thu, 03 May 2018 09:43:11 GMT&quot;,      &quot;vary&quot;: &quot;&quot;,      &quot;cf_ray&quot;: &quot;4151c826ac834704-EWR&quot;,      &quot;server&quot;: &quot;cloudflare&quot;    },    &quot;response_body&quot;: &quot;{\&quot;status\&quot;:\&quot;success\&quot;,\&quot;message\&quot;:\&quot;https:\\/\\/images.dog.ceo\\/breeds\\/affenpinscher\\/n02110627_13221.jpg\&quot;}&quot;,    &quot;response_code&quot;: &quot;200&quot;  }}</code></pre><p>Let's look at the view code which renders the data received from the API.</p><pre><code class="language-elm">view : (Maybe String, Maybe String) -&gt; Html Msgview usernameAndPassword =case usernameAndPassword of(Nothing, Nothing) -&gt; text &quot;&quot;(Just username, Nothing) -&gt; basicAuthenticationView username &quot;&quot;(Nothing, Just password) -&gt; basicAuthenticationView &quot;&quot; password(Just username, Just password) -&gt; basicAuthenticationView username passwordbasicAuthenticationView : String -&gt; String -&gt; Html MsgbasicAuthenticationView username password =[ div [ class &quot;form-row&quot; ][ input[ type_ &quot;text&quot;, placeholder &quot;Username&quot;, value username, onInput (UpdateUsername)][], input[ type_ &quot;password&quot;, placeholder &quot;Password&quot;, value password, onInput (UpdatePassword)][], a[ href &quot;javascript:void(0)&quot;, onClick (RemoveBasicAuthentication)][ text &quot;&quot; ]]]</code></pre><p>To get the desired view we apply following rules.</p><ol><li>Check if both the values are string.</li><li>Check if either of the values is string.</li><li>Assume that both the values are <code>null</code>.</li></ol><p>This works but we can do a better job of modelling it.</p><p>What's happening here is that we were trying to translate our API responsesdirectly to the Model . Let's try to club username and password together into anew type called <em>BasicAuthentication</em>.</p><p>In the model add a parameter called <em>basicAuthentication</em> which would be of type<code>Maybe BasicAuthentication</code>. This way if user has opted to use basicauthentication fields then it is a <em>Just BasicAuthentication</em> and we can showthe input boxes. Otherwise it is <em>Nothing</em> and we show nothing!</p><p>Here is what the updated Model for <em>Request.MainRequest</em> would look like.</p><pre><code class="language-elm">type alias BasicAuthentication ={ username : String, password : String}type alias APISnapshotRequest ={ url : String, httpMethod : HttpMethod, requestParameters : RequestParameters, requestHeaders : RequestHeaders, basicAuthentication : Maybe BasicAuthentication, requestBody : Maybe RequestBody}type alias Model ={ request : APISnapshotRequest, showErrors : Bool}</code></pre><p>Elm compiler is complaining that we need to make changes to JSON decoding for<em>APISnapshotRequest</em> type because of this change.</p><p>Before we fix that let's take a look at how JSON decoding is currently beingdone.</p><pre><code class="language-elm">import Json.Decode as JDimport Json.Decode.Pipeline as JPdecodeAPISnapshotRequest : Response -&gt; APISnapshotRequestdecodeAPISnapshotRequest hitResponse =letresult =JD.decodeString requestDecoder hitResponse.bodyincase result ofOk decodedValue -&gt;decodedValue            Err err -&gt;                emptyRequestrequestDecoder : JD.Decoder APISnapshotRequestrequestDecoder =JP.decode Request|&gt; JP.optional &quot;username&quot; (JD.map Just JD.string) Nothing|&gt; JP.optional &quot;password&quot; (JD.map Just JD.string) Nothing</code></pre><p>Now we need to derive the state of the application from our API response .</p><p>Let's introduce a type called <em>ReceivedAPISnapshotRequest</em> which would be theshape of our old <em>APISnapshotRequest</em> with no <em>basicAuthentication</em> field. Andlet's update our <em>requestDecoder</em> function to return a Decoder of type<em>ReceivedAPISnapshotRequest</em> instead of <em>APISnapshotRequest</em>.</p><pre><code class="language-elm">type alias ReceivedAPISnapshotRequest ={ url : String, httpMethod : HttpMethod, requestParameters : RequestParameters, requestHeaders : RequestHeaders, username : Maybe String, password : Maybe String, requestBody : Maybe RequestBody}requestDecoder : JD.Decoder ReceivedAPISnapshotRequest</code></pre><p>We need to now move our earlier logic that checks to see if a user has opted touse the basic authentication fields or not from the view function to the<em>decodeAPISnapshotRequest</em> function.</p><pre><code class="language-elm">decodeAPISnapshotRequest : Response -&gt; APISnapshotRequestdecodeAPISnapshotRequest hitResponse =letresult =JD.decodeString requestDecoder hitResponse.bodyincase result ofOk value -&gt;letextractedCreds =( value.username, value.password )                    derivedBasicAuthentication =                        case extractedCreds of                            ( Nothing, Nothing ) -&gt;                                Nothing                            ( Just receivedUsername, Nothing ) -&gt;                                Just { username = receivedUsername, password = &quot;&quot; }                            ( Nothing, Just receivedPassword ) -&gt;                                Just { username = &quot;&quot;, password = receivedPassword }                            ( Just receivedUsername, Just receivedPassword ) -&gt;                                Just { username = receivedUsername, password = receivedPassword }                in                    { url = value.url                    , httpMethod = value.httpMethod                    , requestParameters = value.requestParameters                    , requestHeaders = value.requestHeaders                    , basicAuthentication = derivedBasicAuthentication                    , requestBody = value.requestBody                    }            Err err -&gt;                emptyRequest</code></pre><p>We extract the username and password into <em>extractedCreds</em> as a Pair from<em>ReceivedAPISnapshotRequest</em> after decoding and construct our<em>APISnapshotRequest</em> from it.</p><p>And now we have a clean view function which just takes a <em>BasicAuthentication</em>type and returns us a <em>Html Msg</em> type.</p><pre><code class="language-elm">view : BasicAuthentication -&gt; Html Msgview b =[ div [ class &quot;form-row&quot; ][ input[ type_ &quot;text&quot;, placeholder &quot;Username&quot;, value b.username, onInput (UpdateUsername)][], input[ type_ &quot;password&quot;, placeholder &quot;Password&quot;, value b.password, onInput (UpdatePassword)][], a[ href &quot;javascript:void(0)&quot;, onClick (RemoveBasicAuthentication)][ text &quot;&quot; ]]]</code></pre><p>We now have a Model that better captures the business logic. And should wechange the logic of basic authentication parameter selection in the future, Wedo not have to worry about updating the logic in the view .</p>]]></content>
    </entry><entry>
       <title><![CDATA[APISnapshot built on Elm & Rails is open source]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/apisnapshot-built-using-elm-and-ruby-on-rails-is-open-source"/>
      <updated>2018-05-25T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/apisnapshot-built-using-elm-and-ruby-on-rails-is-open-source</id>
      <content type="html"><![CDATA[<p>APISnapshot (Link not available) is built using Elm and Ruby on Rails. Todaywere happy to announce that the code is publicly<a href="https://github.com/bigbinary/apisnapshot">available on GitHub</a>.</p><p>We built APISnapshot for two reasons.</p><p>We wanted to work with <a href="http://elm-lang.org">Elm</a>.</p><p>We wanted to have a tool that is easy to use and that will help us capture whatresponse we are getting from the API in a format that is easy to share in githubissue, in slack or in an email. As a consulting company we work with variousteams around the world and during development phase either API is unstable orthey do not do what they should be doing.</p><p>We originally built this tool using <a href="https://reactjs.org">React</a>. Elm compileris quite strict and forced us to take into consideration all possibilities. Thislead us to notice a few bugs which were still present in React code. In this wayElm compiler helped us produce &quot;correct&quot; software by eliminating some of thebugs that we would have found later.</p><p>JSON encoding/decoding is a hard problem in Elm in general. In most of the caseswe know the shape of the API response we are going to get.</p><p>In the case of APISnapshot we do not know the shape of the JSON response we willget. Because of that it took us a bit longer to build the application. However,this forced us to really dig deep into JSON encoding/decoding issue in Elm andwe learned a lot.</p><p>We would like to thank all the<a href="https://github.com/bigbinary/apisnapshot/graphs/contributors">contributors</a> tothe project. Special shout out goes to <a href="https://github.com/jasim">Jasim</a> for thediscussion and the initial work on parsing the JSON file.</p><p>We like the combination of Elm and Ruby on Rails so much so that we are building<a href="https://www.acehelp.com">AceHelp</a> using the same technologies. AceHelp is<a href="https://github.com/bigbinary/acehelp">open source</a> from day one.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Generating filmstrip using puppeteer for better debugging]]></title>
       <author><name>Rohit Kumar</name></author>
      <link href="https://www.bigbinary.com/blog/generating-filmstrip-using-puppeteer-for-better-debugging"/>
      <updated>2018-05-24T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/generating-filmstrip-using-puppeteer-for-better-debugging</id>
      <content type="html"><![CDATA[<p>We are writing a lot of automation tests using<a href="https://github.com/GoogleChrome/puppeteer">Puppeteer</a>.</p><p>Since puppeteer scripts execute so fast certain tests fail when they should bepassing. Debugging those tests can be a challenge.</p><p>Chrome devtools comes with<a href="https://www.youtube.com/watch?v=r1LVAu1BB8Y">filmstrip feature</a>. In&quot;Performance&quot; tab we can see screenshots of the site as they change over time.</p><p>We wanted puppeteer to generate similar filmstrip so that we can visuallyidentify the source of the problem.</p><p>It turns out that puppeteer makes it very easy. Here is the full code.</p><pre><code class="language-javascript">import puppeteer from &quot;puppeteer&quot;;(async () =&gt; {  const browser = await puppeteer.launch({ headless: false });  const page = await browser.newPage();  await page.setViewport({ width: 1280, height: 1024 });  await page.tracing.start({ path: &quot;trace.json&quot;, screenshots: true });  await page.goto(&quot;https://www.bigbinary.com&quot;);  await Promise.all([    page.waitForNavigation(),    page.click(&quot;#navbar &gt; ul &gt; li:nth-child(1) &gt; a&quot;),  ]);  await page.tracing.stop();  await browser.close();})();</code></pre><p>If we execute this script then it will generate a file called <code>trace.json</code>. Thisfile has images embedded in it which are base64 encoded.</p><p>To see the filmstrip drag the <code>trace.json</code> file to &quot;Performance&quot; tab in theChrome devtool. Here is a quick video explaining this.</p><p>&lt;iframewidth=&quot;560&quot;height=&quot;315&quot;src=&quot;https://www.youtube.com/embed/hZGTIyc3Xak&quot;frameborder=&quot;0&quot;allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot;allowfullscreen</p><blockquote><p>&lt;/iframe&gt;</p></blockquote>]]></content>
    </entry><entry>
       <title><![CDATA[Practical usage of identity function]]></title>
       <author><name>Rohit Kumar</name></author>
      <link href="https://www.bigbinary.com/blog/practical-usage-of-identity-function"/>
      <updated>2018-03-20T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/practical-usage-of-identity-function</id>
      <content type="html"><![CDATA[<p>If you are learning functional programming then you can't go far<a href="https://gist.github.com/Avaq/1f0636ec5c8d6aed2e45">without running into</a>&quot;identity function&quot;.</p><p>An identity function is a very basic function that</p><ul><li>takes one argument</li><li>returns the argument</li></ul><pre><code class="language-javascript">f(x) = x;</code></pre><p>This seems like the most useless function in the world. We never needed anyfunction like this while building any application. Then what's the big dealabout this identity function.</p><p>In this blog we will see how this identity concept is used in the real world.</p><p>For the implementation we will be using <a href="http://ramdajs.com/">Ramda.js</a>. Wepreviously<a href="https://blog.bigbinary.com/2017/10/06/optimize-javascript-code-for-composability-with-ramdajs.html">wrote about</a>how we, at BigBinary, write JavaScript code using Ramda.js.</p><p>Again please note that in the following code <code>R</code> stands for <code>Ramda</code> and not for<a href="https://www.r-project.org">programming language R</a>.</p><h3>Example 1</h3><p>Here is JavaScript code.</p><pre><code class="language-javascript">if (x) return x;return [];</code></pre><p>Here is same code using Ramda.js.</p><pre><code class="language-javascript">R.ifElse(R.isNil, () =&gt; [], R.identity);</code></pre><p><a href="http://ramdajs.com/repl/?v=0.25.0#?const%20fn%20%3D%20R.ifElse%28%0A%20%20R.isNil%2C%0A%20%20%28%29%20%3D%3E%20%5B%5D%2C%0A%20%20R.identity%0A%20%29%3B%0A%0Afn%28null%29%3B%0Afn%28%22hello%22%29%3B">try it</a></p><h3>Example 2</h3><p>Here we will use identity as the return value in the default case.</p><pre><code class="language-javascript">R.cond([  [R.equals(0), R.always(&quot;0&quot;)],  [R.equals(10), R.always(&quot;10&quot;)],  [R.T, R.identity],]);</code></pre><p><a href="http://ramdajs.com/repl/?v=0.25.0#?const%20fn%20%3D%20R.cond%28%5B%0A%20%20%5BR.equals%280%29%2C%20R.always%28%220%22%29%5D%2C%0A%20%20%5BR.equals%2810%29%2C%20R.always%28%2210%22%29%5D%2C%0A%20%20%5BR.T%2C%20R.identity%5D%0A%5D%29%3B%0A%0A%0Afn%280%29%3B%0Afn%2810%29%3B%0Afn%285%29%3B">try it</a></p><h3>Example 3</h3><p>Get the unique items from the list.</p><pre><code class="language-javascript">R.uniqBy(R.identity, [1, 1, 2]);</code></pre><p><a href="http://ramdajs.com/repl/?v=0.25.0#?R.uniqBy%28R.identity%2C%20%5B1%2C1%2C2%5D%29">try it</a></p><h3>Example 4</h3><p>Count occurrences of items in the list.</p><pre><code class="language-javascript">R.countBy(R.identity, [&quot;a&quot;, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;c&quot;, &quot;c&quot;]);</code></pre><p><a href="http://ramdajs.com/repl/?v=0.25.0#?R.countBy%28R.identity%2C%20%5B%22a%22%2C%22a%22%2C%22b%22%2C%22c%22%2C%22c%22%2C%22c%22%5D%29%3B">try it</a></p><h3>Example 5</h3><p>Begin value from zero all the way to n-1.</p><pre><code class="language-javascript">R.times(R.identity, 5);</code></pre><p><a href="http://ramdajs.com/repl/?v=0.25.0#?R.times%28R.identity%2C%205%29">try it</a></p><h3>Example 6</h3><p>Filter truthy values.</p><pre><code class="language-javascript">R.filter(R.identity, [  { a: 1 },  false,  { b: 2 },  true,  &quot;&quot;,  undefined,  null,  0,  {},  1,]);</code></pre><p><a href="http://ramdajs.com/repl/#?R.filter%28R.identity%2C%20%5B%7Ba%3A1%7D%2C%20false%2C%20%7Bb%3A2%7D%2C%20true%2C%20%27%27%2C%20undefined%2C%20null%2C%200%2C%20%7B%7D%2C%201%5D%29%3B">try it</a></p>]]></content>
    </entry><entry>
       <title><![CDATA[Fixing CORS issue with AWS services]]></title>
       <author><name>Narendra Rajput</name></author>
      <link href="https://www.bigbinary.com/blog/fixing-cors-issue-with-aws-services"/>
      <updated>2017-10-31T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/fixing-cors-issue-with-aws-services</id>
      <content type="html"><![CDATA[<p>While working on a client project, we started facing an issue where the JWPlayerstopped playing videos when we switched to<a href="https://en.wikipedia.org/wiki/HTTP_Live_Streaming">hls</a> version of videos. Wefound a<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS</a>error in the JS console as shown below.</p><p><img src="/blog/images/images_used_in_blog/2017/fixing-cors-issue-with-aws-services/cors_error.png" alt="cors error"></p><p>After researching we found that JWPlayer makes an AJAX request to load the<a href="https://en.wikipedia.org/wiki/M3U#M3U8">m3u8</a> file. To fix the issue, we neededto enable CORS and for that we needed to make changes to S3 and Cloudfrontconfigurations.</p><h2>S3 configuration changes</h2><p>We can configure CORS for the S3 bucket by allowing requests originating fromspecified hosts. As show in the image below we can find the CORS configurationoption in Permissions tab of the S3 bucket.<a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html">Here</a> is the officialdocumentation on configuring CORS for S3.</p><p><img src="/blog/images/images_used_in_blog/2017/fixing-cors-issue-with-aws-services/s3_cors_configuration.png" alt="s3 cors configuration"></p><p>S3 bucket will now allow requests originating from the specified hosts.</p><h2>Cloudfront configuration changes</h2><p><a href="http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html">Cloudfront</a>is a CDN service provided by AWS which uses edge locations to speed up thedelivery of static content. Cloudfront takes content from S3 buckets and cachesit at edge locations and delivers it to the end user. For enabling CORS we needto configure Cloudfront to allow forwarding of required headers.</p><p>We can configure the behavior of Cloudfront by clicking on CloudfrontDistribution's &quot;Distribution Settings&quot;. Then from the &quot;Behaviour&quot; tab click on&quot;Edit&quot;. Here we need to whitelist the headers that need to be forwarded. Selectthe &quot;Origin&quot; header to whitelist which is required for CORS, as shown in theimage below.</p><p><img src="/blog/images/images_used_in_blog/2017/fixing-cors-issue-with-aws-services/cloudfront_behaviour.png" alt="cloudfront behaviour"></p>]]></content>
    </entry><entry>
       <title><![CDATA[Elm Conf 2017 Summary]]></title>
       <author><name>Prathamesh Sonpatki</name></author>
      <link href="https://www.bigbinary.com/blog/elm-conf-2017-summary"/>
      <updated>2017-10-04T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/elm-conf-2017-summary</id>
      <content type="html"><![CDATA[<p>I attended <a href="https://www.elm-conf.us">Elm Conf 2017 US</a> last week alongside<a href="https://www.thestrangeloop.com/">Strangeloop conference</a>. I was looking forwardto the conference to know what the Elm community is working on and what problemspeople are facing and what are they doing to overcome those.</p><p>After attending the conference, I can say that Elm community is growing strong.The conference was attended by around 350 people and many were using Elm inproduction. More number of people wanted to try Elm in production.</p><p>There was a lot of enthusiasm about starting new Elm meetups. As a Ruby on Railsand React meetup organizer myself, I was genuinely interested in hearingexperiences of seasoned meetup organizers. In general Evan and Richard prefermeetup to be a place where people form small groups and hack on something ratherthan one person teaching the whole group something.</p><p>I liked all the <a href="https://www.elm-conf.us/talks/">talks</a>. There was variety inthe topics and the speakers were all seasoned. Kudos to the organizers forputting up a great program. Below is a quick summary of my thoughts from theconference.</p><h3>Keynote by Evan</h3><p><a href="https://twitter.com/czaplic">Evan</a> talked about the work he has been doing forthe upcoming release of Elm. He discussed the optimization work related to codesplitting, code generation and minification for speeding up building anddelivering single page apps using Elm. He made another interesting point that hechanged the codegen which generates the JS code from Elm code twice but nobodynoticed it. Things like this can give a huge opportunity to change and improveexisting designs which he has been doing for the upcoming release.</p><p>In the end he mentioned that his philosophy is not to rush things. It's betterto do things right than doing it now.</p><p>After the keynote, he encouraged people to talk to him about what they areworking on which was really nice.</p><h3>Accessibility with Elm</h3><p><a href="https://twitter.com/t_kelly9">Tessa</a> talked about her work around adding<a href="http://package.elm-lang.org/packages/tesk9/elm-html-a11y">accessibility support</a>for Elm apps. She talked about design decisions, prior art and some of thechallenges she faced while working on the library like working with tabs,interactive elements and images. There was a question at the end about whetherthis will be incorporated into Elm core but Evan mentioned that it might takesome time.</p><h3>Putting the Elm Platform in the Browser</h3><p><a href="https://twitter.com/ellie_editor">Luke</a>, the creator of<a href="https://ellie-app.com/">Ellie</a> - a way to easily share your elm code withothers online - talked about how he started with Ellie. He talked about theproblems he had to face for implementing and sustaining Ellie through ads.During the talk, he also open sourced the code, so we can see it on<a href="https://github.com/lukewestby/ellie">Github</a> now.</p><p>Luke mentioned how he changed the architecture of Ellie from mostly running onthe server to running in the browser using service workers. He discussed futureplans about sustaining Ellie, building an Elm editor instead of usingCodemirror, getting rid of ads and making Ellie better for everyone.</p><h3>The Importance of Ports</h3><p>In other frameworks like <a href="http://www.purescript.org/">PureScript</a> and<a href="https://bucklescript.github.io/">BuckleScript</a> invoking native JavaScriptfunctions is easy. In Elm one has to use &quot;Ports&quot;. Using Ports requires someextra stuff. In return we get more safety.</p><p><a href="https://twitter.com/splodingsocks">Murphy Randle</a> presented a case where he wasusing too many ports which was resulting in fragmented code. He discussed howport is based on <a href="https://en.wikipedia.org/wiki/Actor_model">Actor Model</a> andonce we get that then using port would be much easier. He also showed refactoredcode.</p><p>Murphy also runs Elm Town Podcast (Link is not available). Listen to episode 13to know more about Ports.</p><h3>Keynote by Richard Feldman</h3><p><a href="https://twitter.com/rtfeldman">Richard</a> talked about his experiences inteaching beginners about Elm. He has taught Elm a lot. He has done an extensiveElm course on <a href="https://frontendmasters.com/workshops/elm">Front end masters</a>. Heis currently writing<a href="https://www.manning.com/books/elm-in-action">Elm in Action book</a>.</p><p>He talked about finding motivation to teach using the<a href="http://edglossary.org/swbat/">SWBAT technique</a>. It helped him in deciding theagenda and finding the direct path for teaching. He mentioned that in thebeginning being precise and detailed is not important. This resonated with me asthe most important thing for anyone who is getting started is getting startedwith the most basic things and then iterating over it again and again.</p><h3>Parting thoughts</h3><p>Elm community is small, tight, very friendly and warm. Lots of people are tryinga lot of cool things. <a href="https://elmlang.herokuapp.com/">Elm Slack</a> came in thediscussions again and again as a good place to seek out help for beginners.</p><p>When I heard about Elm first, it was about good compiler errors and having runtime safety. However after attending the conference I am mighty impressed withthe Elm community.</p><p>Big props to <a href="https://twitter.com/brianhicks">Brian</a> and<a href="https://twitter.com/ellie_editor">Luke</a> for organizing the conference!</p><p>All the videos from the conference are already getting<a href="https://www.youtube.com/watch?v=P3pL85n9_5s&amp;list=PLglJM3BYAMPFTT61A0Axo_8n0s9n9CixA">uploaded here</a>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Difference between type and type alias in Elm]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/difference-between-type-and-type-alias-in-elm"/>
      <updated>2017-07-12T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/difference-between-type-and-type-alias-in-elm</id>
      <content type="html"><![CDATA[<p>What is the difference between <code>type</code> and <code>type alias</code>.</p><p>Elm FAQ has<a href="http://faq.elm-community.org/#what-is-the-difference-between-type-and-type-alias">an answer</a>to this question. However I could not fully understand the answer.</p><p>This is my attempt in explaining it.</p><h2>What is type</h2><p>In Elm everything has a type. Fire up <code>elm-repl</code> and you will see 4 is a<code>number</code> and &quot;hello&quot; is a <code>String</code>.</p><pre><code class="language-elm">&gt; 44 : number&gt; &quot;hello&quot;&quot;hello&quot; : String</code></pre><p>Let's assume that we are working with users records and we have followingattributes of those users.</p><ul><li>Name</li><li>Age</li><li>Status (Active or Inactive)</li></ul><p>It's pretty clear that &quot;Name&quot; should be of type &quot;String&quot; and &quot;Age&quot; should be oftype &quot;number&quot;.</p><p>Let's think about a moment what is the type of &quot;Status&quot;. What is &quot;Active&quot; and&quot;Inactive&quot; in terms of type.</p><p><code>Active</code> and <code>Inactive</code> are two valid values of <code>Status</code>. In other programminglanguages we might represent <code>Status</code> as an enum.</p><p>In Elm we need to create a new type. And that can be done as shown here.</p><pre><code class="language-elm">type Status = Active | Inactive</code></pre><p>Second thing we are doing is that we are stating that the valid values for thisnew type are <code>Active</code> and <code>Inactive</code>.</p><p>When I discussed this code with my team members they asked me to show where is<code>Active</code> and <code>Inactive</code> defined. Good question.</p><p>The simple answer is that they are not defined anywhere. They do not need to bedefined. What needs definition is the new type that is being created.</p><p>What makes understanding it a bit hard for people coming from Ruby, Java andsuch background is that these people (including me) are looking at <code>Active</code> and<code>Inactive</code> as a class or a constant which is not the right way to look at.</p><p><code>Active</code> and <code>Inactive</code> are the valid values for type <code>Status</code>.</p><pre><code class="language-elm">&gt; Active-- NAMING ERROR ----------Cannot find variable `Active`3|   Active     ^^^^^^</code></pre><p>As you can see repl is not sure what <code>Active</code> is.</p><p>We can solve this by pasting following code in repl.</p><pre><code class="language-elm">type Status = Active | Inactive</code></pre><p>Now we can run the same code again. This time no error.</p><pre><code class="language-elm">&gt; ActiveActive : Repl.Status</code></pre><h2>What is type alias</h2><p>Let's see a simple application which just prints name and age of a single user.</p><p><a href="https://gist.github.com/neerajsingh0101/60627801877312ea95e328f704e5245a">Here</a>is the code. I'm posting screenshot of the same below with certain parthighlighted.</p><p><img src="/blog/images/images_used_in_blog/2017/difference-between-type-and-type-alias-in-elm/code-without-type-alias.png" alt="code without type alias"></p><p>As you can see <code>{ name : String, age : Int }</code> is repeated at four differentplaces. In a bigger application it might get repeated more often.</p><p>This is what <code>type alias</code> does. It removes repetition. It removes verbosity.</p><p>As the name suggests this is just an alias. Note that <code>type</code> creates a new typewhereas <code>type alias</code> is literally saving keystrokes. <code>type alias</code> does notcreate a new <code>type</code>.</p><p>Now if you read the FAQ answer again then hopefully it will make morse sensenow.</p><p><a href="https://gist.github.com/neerajsingh0101/8e7756a1b7588538ac16526ce2bfc772">Here</a>is modified code using <code>type alias</code>.</p><h2>Why use type alias Username : String</h2><p>While browsing Elm code in general, I came across following code.</p><pre><code class="language-elm">type alias Username = String</code></pre><p>Question is what does code like this buy us. All it does is that instead of<code>String</code> I can now type <code>Username</code>.</p><p>First let's see how it might be used.</p><p>Let's assume that we have a function which returns <code>Status</code> of a user for thegiven username.</p><p>The function might have implementation as shown below.</p><pre><code class="language-elm">getUserStatus username =  make_db_call_and_return_user_status</code></pre><p>Now let's think about what the type annotation (rubyist think of it as methodsignature ) of function <code>getUserStatus</code> might look like.</p><p>It takes <code>username</code> as input and returns user record.</p><p>So the type annotation might look like</p><pre><code class="language-elm">getUserStatus : String -&gt; Status</code></pre><p>This works. However the issue is that <code>String</code> is not expressive enough. It canbe made more expressive if the signature were</p><pre><code class="language-elm">getUserStatus : Username -&gt; Status</code></pre><p>Now that we know about <code>type alias</code> all we need to do is</p><pre><code class="language-elm">type alias Username = String</code></pre><p>This makes code more expressive.</p><h2>No recursion with type alias</h2><p>An example of where we might need recursion is while designing commentingsystem. A comment can have sub-comments. However since <code>type alias</code> is just asubstitution and recursion does not work with it.</p><pre><code class="language-elm">&gt; type alias Comment = { message : String, responses : List Comment }This type alias is recursive, forming an infinite type!2| type alias Comment = { message : String, responses : List Comment }   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^When I expand a recursive type alias, it just keeps getting bigger and bigger.So dealiasing results in an infinitely large type! Try this instead:    type Comment        = Comment { message : String, responses : List Comment }This is kind of a subtle distinction. I suggested the naive fix, but you canoften do something a bit nicer. So I would recommend reading more at:&lt;https://github.com/elm-lang/elm-compiler/blob/0.18.0/hints/recursive-alias.md&gt;</code></pre><p><a href="https://github.com/elm-lang/elm-compiler/blob/master/hints/recursive-alias.md">Hint for Recursive Type Aliases</a>discusses this issue in greater detail and it also has solution to the problemof recursion.</p><h2>Dual role of type alias as constructor and type</h2><p>Let's say that we have following code.</p><pre><code class="language-elm">type alias UserInfo =    { name : String, age : Int }</code></pre><p>Now we can use <code>UserInfo</code> as a constructor to create records.</p><pre><code class="language-elm">&gt; type alias UserInfo = { name : String, age : Int }&gt; sam = UserInfo &quot;Sam&quot; 24{ name = &quot;Sam&quot;, age = 24 } : Repl.UserInfo</code></pre><p>In the above case we used <code>UserInfo</code> as a <strong>constructor</strong> to create new userrecords. We did not use <code>UserInfo</code> as a <code>type</code>.</p><p>Now let's see another function.</p><pre><code class="language-elm">type alias UserInfo =    { name : String, age : Int }getUserAge : UserInfo -&gt; IntgetUserAge userinfo =    userinfo.age</code></pre><p>In this case <code>UserInfo</code> is being used in <strong>type annotation</strong> as <strong>type</strong> and notas <strong>constructor</strong>.</p><h2>Which one to use type or type alias</h2><p>Both of them serve different purpose. Let's see an example.</p><p>Let's say that we have following code.</p><pre><code class="language-elm">type alias UserInfo =    { name : String, age : Int }type alias Coach =    { name : String, age : Int, sports : String }</code></pre><p>Now let's write a function that gets age of the given userinfo.</p><pre><code class="language-elm">getUserAge : UserInfo -&gt; IntgetUserAge UserInfo =    UserInfo.age</code></pre><p>Now let's create two types of users.</p><pre><code class="language-elm">sam = UserInfo &quot;Sam&quot; 24charlie = Coach &quot;Charlie&quot; 52 &quot;Basketball&quot;</code></pre><p>Now let's try to get age of both of these people.</p><pre><code class="language-elm">getUserAge samgetUserAge charlie</code></pre><p>Here is<a href="https://gist.github.com/neerajsingh0101/83f26c0c32c310ab01fe9a27f5bc9e98">the complete version</a>if you want to run it.</p><p><strong>Please note that elm-repl<a href="https://github.com/elm-lang/elm-repl/issues/86">does not support type annotation</a>so you can't test this code in elm-repl.</strong></p><p>The main point here is that since we used <code>type alias</code>, function <code>getUserAge</code>works for both <code>UserInfo</code> as well as <code>Coach</code>. It would be a stretch to say thatthis sounds like &quot;duck typing in Elm&quot; but it comes pretty close.</p><p>Yes Elm is statically typed language and it enforces type. However the pointhere is the <code>type alias</code> is not exactly a type.</p><p>So why did this code work.</p><p>It worked because of Elm's support for<a href="http://elm-lang.org/docs/records#pattern-matching">pattern matching</a> forrecords.</p><p>As mentioned earlier <code>type alias</code> is just a shortcut for typing the verboseversion. So let's expand the type annotation of <code>getUserAge</code>.</p><p>If we were not using <code>type alias UserInfo</code> then it might have looked like asshown below.</p><pre><code class="language-elm">getUserAge : { name : String, age : Int } -&gt; Int</code></pre><p>Here the argument is a record. Here is<a href="http://elm-lang.org/docs/records">official guide on Records</a>. While dealingwith records Elm looks at the argument and if that argument is a record and hasall the matching attributes then Elm will not complain because of its supportfor pattern matching.</p><p>Since <code>Coach</code> has both <code>name</code> and <code>age</code> attribute <code>getUserAge charlie</code> works.</p><p>You can test it by removing the attribute <code>age</code> from <code>Coach</code> and then you willsee that Compiler will complain.</p><p>In summary if we want strict type enforcement then we should go for <code>type</code>. Ifwe need something so that we do not need to type all the attributes all the timeand we want pattern matching then we should go for <code>type alias</code>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Arrows in Elm's method signature]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/arrows-in-method-signature-of-elm"/>
      <updated>2017-07-11T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/arrows-in-method-signature-of-elm</id>
      <content type="html"><![CDATA[<p>Let's look at the documentation of <code>length</code> function of <code>String</code> in Elm.</p><p>It's<a href="http://package.elm-lang.org/packages/elm-lang/core/5.1.1/String#length">here</a>and it looks like this.</p><pre><code class="language-elm">length : String -&gt; Int&gt; String.length &quot;Hello World&quot;11 : Int</code></pre><p>If we look at the similar feature in Ruby world then we get<a href="https://ruby-doc.org/core-2.2.0/String.html#method-i-length">length</a> method.</p><pre><code class="language-ruby">length -&gt; integer</code></pre><p>Method signature in ruby's documentation and Elm's documentation is quitesimilar. Both return an integer.</p><p>In Elm's world method definitions are called &quot;Type Annotations&quot;. Going forwardthat's what I'm going to use in this blog.</p><p>Now let's look at method definition of <code>slice</code> method in Ruby.</p><p>It looks like <a href="https://ruby-doc.org/core-2.2.0/String.html#slice-method">this</a>.</p><pre><code class="language-ruby">slice(start, length) -&gt; new_str or nilirb(main):006:0&gt; &quot;snakes on a plane!&quot;.slice(0,6)=&gt; &quot;snakes&quot;</code></pre><p>In Elm world it looks like<a href="http://package.elm-lang.org/packages/elm-lang/core/5.1.1/String#slice">this</a>.</p><pre><code class="language-ruby">slice : Int -&gt; Int -&gt; String -&gt; String&gt; String.slice  0  6 &quot;snakes on a plane!&quot;&quot;snakes&quot; : String</code></pre><p>Questions is what's up with all those arrows.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Rails 5 blogs and the art of story telling]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/rails-5-blogs-and-the-art-of-story-telling"/>
      <updated>2016-09-19T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/rails-5-blogs-and-the-art-of-story-telling</id>
      <content type="html"><![CDATA[<p>Between October 31,2015 and Sep 5, 2016 we wrote 80 blogs on changes in<a href="/blog/categories/rails-5">Rails 5</a>.</p><p>Producing a blog every 4 days consistently over 310 days takes persistence andtime - lots of it.</p><p>We needed to go through all the commits and then pick the ones which are worthwriting about and then write about it. Going into this I knew it would be a hardtask. Ruby on Rails is now a well crafted machine. In order to fully understandwhat's going on in the code base we need to spend sufficient time on it.</p><p>However I was surprised by the thing that turned to be the hardest - tellingstory of the code change.</p><p>Every commit has a story. There is a reason for it. The commit itself might beminor but that code change in itself does not tell the full story.</p><p>For example take<a href="https://github.com/rails/rails/commit/a71350cae0082193ad8c66d65ab62e8bb0b7853b">this commit</a>.This commit is so simple that you might think it is not worth writing about.However in order to fully understand what it does we need to tell the full storywhich was captured in<a href="rails-5-disables-autoloading-after-booting-the-app-in-production">this blog</a>.</p><p>Or take the case of<a href="rails-5-official-supports-mariadb">Rails 5 officially supports MariaDB</a> . Theblog captures the full story and not just the code that changed.</p><p>Now you might say that I have cherry picked blog posts that favor my case. Solet's pick a blog <a href="skip-mailers-while-generating-rails-5-app">which is simple</a>.</p><p>You might wonder what could go wrong with a blog like this. As it turns out,plenty. That's because writing a blog also requires defining the boundary of theblog. Deciding what to include and what to leave out is hard. One gets a feelfor it only after writing it. And after having typed the words on screen,<a href="https://m.signalvnoise.com/the-writing-class-id-like-to-teach-11b259f44a5d#.mk324kxym">pruning is hard</a>.</p><p>A good written article is simple writing. The problem with article which aresimple to readers is that - well it is simple. So it feels to readers thatwriting it must be simple. Nothing can be further from the truth. It takes a lotof hard work to produce anything simple. It's true in writing. And it's true inproducing software.</p><p>Coming back to the &quot;Skipping Mailer&quot; blog, it took quite a bit of back and forthto bring the blog to its essence. So yes the final output is quite short butthat does not mean that it took short amount of time to produce it.</p><h2>Tell a story even if you have 10 seconds</h2><p>John Lasseter was working as an animator at Disney in 1984. He was just firedfrom Disney for promoting computer animations at Disney. Lasseter joinsLucasfilm. Lucasfilm renamed itself to Pixar Graphics Group and sold itself toSteve Jobs for $5 million.</p><p>Lasseter was tasked with producing a short film that would show the power ofwhat computer animations could do so that Pixar Graphics Group can get someprojects like producing TV commercials with cartoon characters and earn somemoney. Lasseter needed to produce a short film for the upcoming computergraphics animation conference.</p><p>His initial idea was to have a short movie having a plotless character. Hepresented this idea to a conference in Brussels. There Belgian animator RaoulServais commented in slightly harsh tone that</p><blockquote><p>No matter how short it is, it should have a beginning, a middle, and an end.Don't forget the story.</p></blockquote><p>Lasseter complained that it's a pretty short movie and there might not be timeto present a story.</p><p>Raoul Servais replied</p><blockquote><p>You can tell a story in ten seconds.</p></blockquote><p>Lasseter started developing a character. He came up with the idea of <em>Luxo Jr.</em></p><p><a href="https://www.youtube.com/watch?v=6G3O60o5U7w">Here is</a> final production of<strong>Luxo Jr.</strong></p><p>Luxo Jr. was a major hit at the conference. Crowd was on its feet in applauseeven before the two minutes film was over. Remember this is 1986 and ComputerAnimation was not much advanced at that time and this was the first movie evermade with the use of just computer graphics.</p><p>Lasseter later said that when audience was watching the movie they forgot thatthey were watching a computer animated film because the story took over them. Helearned the lesson that technology should enable better story telling andtechnology in itself divorced from story telling would not advance the cause ofPixar.</p><p>Later John Lasseter went on to produce hits like Toy Story, A bug's life, ToyStory 2, Cars, Cars 2, Monsters Inc, Finding Nemo and many more.</p><p>So you see even a great John Lasseter had to be reminded to tell a story.</p><h2>Actual content over bullet points</h2><p><a href="https://en.wikipedia.org/wiki/Jeff_Bezos">Jeff Bezos</a> is so focused on knowingthe full story that he banned usage of PowerPoint in internal meetings anddiscussions. As per him it is easy to hide behind bullet points in a PowerPointpresentation.</p><p>He insisted on writing the full story in word document and distribute it tomeeting attendees. The meetings starts with everyone head down reading thedocument.</p><p>He is also known for saying that if we are building a feature then we first needto know how it would be presented to the consumers when it is unveiled. We needto know the story we are going to tell them. Without the story we won't havefull picture of what we are going to build.</p><h2>Learning to tell story is a journey</h2><p>I'm glad that during the last 310 days 16 people contributed to the blog posts.The process of writing the posts at times was frustrating for a bunch of them.They had done the work of digging into the code and had posted their findings.Continuously getting feedback to edit the blog to build a nice coherent storywhere each paragraph is an extension of the previous paragraph is a downer. Somewere dismayed at why we are spending so much energy on a technical blog.</p><p>However in the end we all are happy that we underwent this exercise. We couldsee the initial draft of the blog and the final version and we all could see thedifference.</p><p>By no means we have mastered the art of storytelling. It's a long journey.However we believe we are on the right path. Hopefully in coming months andyears we at BigBinary would be able to bring to you more stories from changes inRails and other places.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Configure PostgreSQL to allow remote connection]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/configure-postgresql-to-allow-remote-connection"/>
      <updated>2016-01-23T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/configure-postgresql-to-allow-remote-connection</id>
      <content type="html"><![CDATA[<p>By default PostgreSQL is configured to be bound to &quot;localhost&quot;.</p><pre><code class="language-plaintext">$ netstat -nltProto Recv-Q Send-Q Local Address           Foreign Address         Statetcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTENtcp        0      0 127.0.0.1:11211         0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTENtcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTENtcp        0      0 127.0.0.1:3737          0.0.0.0:*               LISTENtcp6       0      0 :::22                   :::*                    LISTEN</code></pre><p>As we can see above port <code>5432</code> is bound to <code>127.0.0.1</code>. It means any attempt toconnect to the postgresql server from outside the machine will be refused. Wecan try hitting the port <code>5432</code> by using telnet.</p><pre><code class="language-plaintext">$ telnet 107.170.11.79 5432Trying 107.170.11.79...telnet: connect to address 107.170.11.79: Connection refusedtelnet: Unable to connect to remote host</code></pre><h2>Configuring postgresql.conf</h2><p>In order to fix this issue we need to find <code>postgresql.conf</code>. In differentsystems it is located at different place. I usually search for it.</p><pre><code class="language-plaintext">$ find / -name &quot;postgresql.conf&quot;/var/lib/pgsql/9.4/data/postgresql.conf</code></pre><p>Open <code>postgresql.conf</code> file and replace line</p><pre><code class="language-plaintext">listen_addresses = 'localhost'</code></pre><p>with</p><pre><code class="language-plaintext">listen_addresses = '*'</code></pre><p>Now restart postgresql server.</p><pre><code class="language-plaintext">$ netstat -nltProto Recv-Q Send-Q Local Address           Foreign Address         Statetcp        0      0 127.0.0.1:11211         0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTENtcp        0      0 0.0.0.0:5432            0.0.0.0:*               LISTENtcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTENtcp        0      0 127.0.0.1:2812          0.0.0.0:*               LISTENtcp6       0      0 ::1:11211               :::*                    LISTENtcp6       0      0 :::22                   :::*                    LISTENtcp6       0      0 :::5432                 :::*                    LISTENtcp6       0      0 ::1:25                  :::*                    LISTEN</code></pre><p>Here we can see that &quot;Local Address&quot; for port <code>5432</code> has changed to <code>0.0.0.0</code>.</p><h2>Configuring pg_hba.conf</h2><p>Let's try to connect to remote postgresql server using &quot;psql&quot;.</p><pre><code class="language-plaintext">$ psql -h 107.170.158.89 -U postgrespsql: could not connect to server: Connection refusedIs the server running on host &quot;107.170.158.89&quot; and acceptingTCP/IP connections on port 5432?</code></pre><p>In order to fix it, open <code>pg_hba.conf</code> and add following entry at the very end.</p><pre><code class="language-plaintext">host    all             all              0.0.0.0/0                       md5host    all             all              ::/0                            md5</code></pre><p>The second entry is for IPv6 network.</p><p>Do not get confused by &quot;md5&quot; option mentioned above. All it means is that apassword needs to be provided. If you want client to allow collection withoutproviding any password then change &quot;md5&quot; to &quot;trust&quot; and that will allowconnection unconditionally.</p><p>Restart postgresql server.</p><pre><code class="language-plaintext">$ psql -h 107.170.158.89 -U postgresPassword for user postgres:psql (9.4.1, server 9.4.5)Type &quot;help&quot; for help.postgres=# \l</code></pre><p>You should be able to see list of databases.</p><p>Now we are able to connect to postgresql server remotely.</p><p>Please note that in the real world you should be using extra layer of securityby using &quot;iptables&quot;.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Year in review 2015]]></title>
       <author><name>Vipul</name></author>
      <link href="https://www.bigbinary.com/blog/year-in-review-2015"/>
      <updated>2016-01-04T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/year-in-review-2015</id>
      <content type="html"><![CDATA[<p>Year 2015 was an exciting year for BigBinary !</p><p>We added more <a href="https://bigbinary.com/clients">clients</a>. We increased<a href="https://bigbinary.com/team">our team</a> size. We wrote more <a href="/blog">blogs</a>, spokeat more <a href="https://bigbinary.com/presentations">conferences</a>, and made even<a href="https://bigbinary.com/videos">more videos</a> !</p><p>Here is the breakdown.</p><h2>We love conferences</h2><p>We presented at 10 conferences across 7 countries, on topics from<a href="http://rubyonrails.org/">Rails</a> to<a href="https://facebook.github.io/react/">ReactJS</a>.</p><ul><li><a href="http://www.gardencityruby.org/">Garden City RubyConf, India</a></li><li><a href="http://rubyconf.ph/">RubyConf, Philippines</a></li><li><a href="http://rubyconfindia.org/">RubyConf, India</a></li><li><a href="http://www.reddotrubyconf.com/">Reddot RubyConf, Singapore</a></li><li><a href="http://www.deccanrubyconf.org/">Deccan RubyConf, India</a></li><li><a href="http://2015.fullstackfest.com/">Full Stack Fest, Spain</a></li><li><a href="http://2015.rubyconf.tw/">RubyConf, Taiwan</a></li><li><a href="http://rockymtnruby.com/">Rocky Mountain Ruby, Colorado</a></li><li><a href="http://www.rubyconf.co/">RubyConf, Colombia</a></li></ul><h2>We are all in ReactJS</h2><p>We at BigBinary adopted ReactJS pretty early. Early in the year we publishedseries of videos titled<a href="https://bigbinary.com/videos/learn-reactjs-in-steps">Learn ReactJS in steps</a>which takes a &quot;Hello World&quot; app into a full TODO application using ReactJS inincremental steps.</p><p>Vipul and Prathamesh are currently authoring a book on ReactJS. Check it out at<a href="https://www.packtpub.com/web-development/reactjs-example-building-modern-web-applications-react">ReactJS by Example- Building Modern Web Applications with React Book</a></p><p>We also started an ios app using React Native. It's coming along pretty good andsoon we should see it in app store.</p><h2>We authored many blogs</h2><p>We love sharing our experiences via blogs on various topics like ReactJS, Rails,React Native and Robot framework etc. Here are some of our blogs from 2015.</p><ul><li><a href="how-to-obtain-current-time-from-a-different-timezone-in-selenium-ide-using-javascript">How to obtain current time from a different timezone in Selenium IDE using javascript</a>,Prabhakar, Jan 2015</li><li><a href="author-information-in-jekyll-blog">Author information in jekyll blog</a>,Neeraj, Jan 2015</li><li><a href="phone-verification-using-twilio">Phone verification using SMS via Twilio</a>,Santosh, Jan 2015</li><li><a href="blue-border-around-jwplayer-video">Blue border around JWPLAYER video</a>,Prathamesh, Feb 2015</li><li><a href="gotcha-with-after_commit-callback-in-rails">Gotcha with after_commit callback in Rails</a>,Prathamesh, March 2015</li><li><a href="voice-based-phone-verification-using-twilio">Voice based phone verification using twilio</a>,Santosh, March 2015</li><li><a href="verifying-pubsub-services-from-rails-redis">Verifying PubSub Services from Rails using Redis</a>,Vipul, May 2015</li><li><a href="using-reactjs-with-rails-actioncable">Using ReactJS with Rails Action Cable</a>,Vipul, July 2015</li><li><a href="how-to-test-react-native-app-on-real-iphone">How to test React Native App on a real iPhone</a>,Chirag, Aug 2015</li><li><a href="code-optimize-javascript-code-using-babeljs">Optimize JavaScript code using BabelJS</a>,Prathamesh, Aug 2015</li><li><a href="configuring-pycharm-to-run-tests">Configuring Pycharm IDE to run a Robot Framework test suite or a single test script</a>,Prabhakar, Oct 2015</li><li><a href="migrating-from-postgresql-to-sqlserver">Migrating rails app from postgresql to sql server</a>,Rohit, Oct 2015</li><li><a href="getting-around-apple-ituneconnect-activation-issue">Getting around Apple iTunesConnect account activation issue</a>,Prathamesh, Oct 2015</li><li><a href="rails-5-allows-setting-custom-http-headers-for-assets">Rails 5 allows setting custom HTTP Headers for assets</a>,Vipul, Oct 2015</li><li><a href="using-stripe-api-in-react-native-with-fetch">Using Stripe API in React Native with fetch</a>,Chirag, Nov 2015</li><li><a href="how-constant-lookup-happens-in-rails">How constant lookup and resolution works in Ruby on Rails</a>,Mohit, Nov 2015</li><li><a href="explicitly-ssh-into-vagrant-machine">Explicitly ssh into vagrant machine</a>,Neeraj, Dec 2015</li><li><a href="application-record-in-rails-5">ApplicationRecord in Rails 5</a>, Prathamesh, Dec2015</li></ul><h2>Video Summary</h2><ul><li><a href="https://bigbinary.com/videos/learn-ruby-on-rails">Learn Ruby on Rails</a><ul><li><a href="https://bigbinary.com/videos/learn-ruby-on-rails/use-uuid-x-request-id-and-tagged-logging-to-debug-rails-application">Use uuid, X-Request-Id and tagged logging to debug rails application</a>.</li><li><a href="https://bigbinary.com/videos/learn-ruby-on-rails/rails-development-using-vagrant">Rails development using vagrant</a>.</li><li><a href="https://bigbinary.com/videos/learn-ruby-on-rails/using-es6-in-rails-application">Using ES6 in Rails application</a>.</li></ul></li><li><a href="https://bigbinary.com/videos/learn-reactjs-in-steps">Learn ReactJS in step</a></li><li><a href="https://bigbinary.com/videos/keep-up-with-reactjs">Keep up with ReactJS</a></li><li><a href="https://bigbinary.com/videos/learn-javascript">Learn JavaScript</a><ul><li><a href="https://bigbinary.com/videos/learn-javascript/refactor-javascript-code-using-module-pattern">Refactor JavaScript code using module pattern</a></li><li><a href="https://bigbinary.com/videos/learn-javascript/a-review-of-tools-to-test-es6">A review of tools to test ES6</a></li></ul></li><li><a href="https://bigbinary.com/videos/learn-selenium">Learn Selenium</a></li></ul><h2>Open Source</h2><p>Apart from our team members contributing to various OpenSource projects, we alsosupport some projects from our team. This year, we added and helped buildfollowing projects-</p><ul><li><a href="https://github.com/bigbinary/wheel">Wheel</a> : Wheel is our Rails template fornew Ruby on Rails projects, with sane defaults and setups for differentenvironments, and common functionalities like image uploaders, debugging, etc.</li><li><a href="https://github.com/bigbinary/mail_interceptor">Mail Interceptor</a> :Interception, Forwarding emails in Ruby on Rails application</li><li><a href="https://github.com/bigbinary/handy">Handy</a> : A collection handy tools andRails tasks for your Project.</li><li><a href="https://github.com/bigbinary/learn-reactjs-in-steps">Learn ReactJS in Steps</a>: Collection of examples from<a href="https://bigbinary.com/videos/learn-reactjs-in-steps">Learn ReactJS in step</a>video series.</li><li>Doctsplit Chef(Link is not available) : Check cookboxfor <a href="https://documentcloud.github.io/docsplit/">docsplit</a> ruby gem</li><li>Fixtures Dumper(Link is not available) : Dump yourRails data to fixtures easily to a database to populate data.</li></ul><h2>Community Engagement</h2><p>Along with speaking at various conferences, we also helped organize, our Funedition of Pune's regional RubyConf,<a href="http://www.deccanrubyconf.org/">DeccanRubyConf</a>, and supported other IndianConferences, including <a href="http://rubyconfindia.org/">RubyConfIndia</a>,<a href="http://www.gardencityruby.org/">GardenCity RubyConf</a>.</p><p>We also help Pune's local <a href="http://www.meetup.com/punerailsmeetup/">ruby meetup</a>,which had a splendid engagement this year.</p><p>Overall we are super excited about what we accomplished in year 2015. We arelooking forward to an exciting year of 2016!</p>]]></content>
    </entry><entry>
       <title><![CDATA[Explicitly ssh into vagrant machine]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/explicitly-ssh-into-vagrant-machine"/>
      <updated>2015-12-27T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/explicitly-ssh-into-vagrant-machine</id>
      <content type="html"><![CDATA[<p>After building vagrant machine the command to ssh into the guest machine ispretty simple.</p><pre><code class="language-plaintext">vagarnt ssh</code></pre><p>While working with chef I needed to explicitly ssh into the vagrant machine. Ittook me sometime to figure it out.</p><p>The key is command <code>vagrant ssh-config</code>. The output might look like this.</p><pre><code class="language-plaintext">$ vagrant ssh-configHost vmachine  HostName 127.0.0.1  User vagrant  Port 2222  UserKnownHostsFile /dev/null  StrictHostKeyChecking no  PasswordAuthentication no  IdentityFile /Users/nsingh/code/vagrant-machine/.vagrant/machines/vmachine/virtualbox/private_key  IdentitiesOnly yes  LogLevel FATAL  ForwardAgent yes</code></pre><p>Open <code>~/.ssh/config</code> and paste the output at the end of the file and save thefile.</p><p>Now I can ssh into vagrant machine using ssh command as shown below.</p><pre><code class="language-plaintext">ssh vmachine</code></pre><p>If you are wondering from where the name <code>vmachine</code> came then this is the name Ihad given to<a href="https://github.com/bigbinary/vagrant-machine/blob/f5257dc088dfdf07c73e57130425c28a363dc399/Vagrantfile#L17">my vagrant machine</a>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Configuring Pycharm IDE to run a Robot Framework test]]></title>
       <author><name>Prabhakar Battula</name></author>
      <link href="https://www.bigbinary.com/blog/configuring-pycharm-to-run-tests"/>
      <updated>2015-10-11T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/configuring-pycharm-to-run-tests</id>
      <content type="html"><![CDATA[<p><a href="https://www.jetbrains.com/pycharm">Pycharm</a> is a convenient IDE to work with<a href="http://robotframework.org">Robot framework</a>. To run a test suite or a testscript, one can do so only through console. Running tests through console isvery demanding. If user can run tests from Pycharm itself then that helpsimprove productivity. This blog explains how to configure Pycharm to be able torun test suite or a single test from the IDE itself.</p><h2>Configuration to run a single test script</h2><p><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p1.png" alt="pycharm robot 1"><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p2.png" alt="pycharm robot 2"><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p3.png" alt="pycharm robot 3"></p><p>Running a single test script</p><p><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p4.png" alt="pycharm robot 4"></p><h2>Configuration to run a particular test suite</h2><p><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p5.png" alt="pycharm robot 5"></p><p>Running a particular test suite</p><p><img src="/blog/images/images_used_in_blog/2015/configuring-pycharm-to-run-tests/p6.png" alt="pycharm robot 6"></p>]]></content>
    </entry><entry>
       <title><![CDATA[Blue border around JWPLAYER video]]></title>
       <author><name>Prathamesh Sonpatki</name></author>
      <link href="https://www.bigbinary.com/blog/blue-border-around-jwplayer-video"/>
      <updated>2015-02-21T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/blue-border-around-jwplayer-video</id>
      <content type="html"><![CDATA[<p>Latest versions of JWPlayer(6.9 onwards)adds blue border around the videowhen it is in focus.</p><p>This is because of the CSS class <code>jwplayer-tab-focus</code>.</p><p>The blue borderaround currently selected videoallows to identifywhich instance of JWPlayer is in focus.</p><p>But with a single JWPlayer instance, it can be annoying.</p><p>To remove this blue border,we can override the default JWPlayer CSS as follows.</p><pre><code class="language-css">.jw-tab-focus:focus {  outline: none;}</code></pre><p>To keep all the overridden CSS in once place,we can add this change in a separate file such as <code>jwplayer_overrides.css</code>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[2014 - Year in Community Engagement]]></title>
       <author><name>Vipul</name></author>
      <link href="https://www.bigbinary.com/blog/2014-year-in-community-engagement"/>
      <updated>2015-01-01T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/2014-year-in-community-engagement</id>
      <content type="html"><![CDATA[<p>At BigBinary our team loves to engage with the community as well as help out indifferent ways. We love contributing to <a href="https://bigbinary.com/open-source">OpenSource</a>,<a href="https://bigbinary.com/presentations">speak and attend different conferences</a>, and helpout in community meetups, <a href="deccanrubyconf">organizing conferences</a>,and events like <a href="rails-girls-pune-2014">RailsGirls</a> .</p><h2>Short summary</h2><p>In year 2014we presented atfollowing 8 conferencesacross 6 countries.</p><ul><li>RubyConf Goa, India</li><li>RubyConf Philippines</li><li>RedDotRubyConf Singapore</li><li>DeccanRubyConf Pune, India</li><li>Madion+Ruby Wisconsin, USA</li><li>RubyConf Brazil</li><li>RubyKaigi Tokyo, Japan</li><li>Golden Gate Ruby Conference San Francisco, USA</li></ul><h2>Rails and Ruby Conferences</h2><p>&lt;iframe style=&quot;min-height: 500px; width: 100%;&quot;src=&quot;https://www.mapquest.com/embed?hk=1x7D17Q&quot;marginwidth=&quot;0&quot;marginheight=&quot;0&quot;frameborder=&quot;0&quot;scrolling=&quot;no&quot;&gt;&lt;/iframe&gt;</p><p>At the start of our travel, we visited <a href="http://rubyconfindia.org">RubyConfIndia 2014</a>.The conference took place at an amazing beach resort in Goa. The two days of conferencewere full of fun and interactions with the best ruby people around India. During theconference our team announced launch of <a href="http://www.rubyindia.org/">Ruby India</a> to helpspread ideas and experiments from the Ruby Indian Community, as well as highlightcontent from people.</p><p>Soon after, I visited Philippines, to conduct a workshop on &quot;Contributing to Rails&quot; at<a href="http://rubyconf.ph">RubyConf Philippines</a>. It was amazing to meet the growingPhilippines community. I was happy to spend time with some amazing Rubyists, like<a href="https://twitter.com/apotonick">Nick Sutterer</a>, <a href="https://twitter.com/_zzak">Zachary Scott</a>,<a href="https://twitter.com/konstantinhaase">Konstantin Hasse</a>,<a href="https://twitter.com/_ko1">Koichi Sasada San</a>,<a href="https://twitter.com/aspleenic">PJ Hagerty</a>,<a href="https://twitter.com/indirect">Andre Arko</a> and so on.</p><p>After that I and Prathamesh went to Singapore to speak at<a href="http://reddotrubyconf.com">RedDotRubyConf</a>. We spoke on Arel andActiveRecord. &quot;RedDorRubyConf&quot; was our first joint talk together at a conference.Again we met a lot of awesome people like <a href="https://twitter.com/_solnic_">Piotr Solnica</a>,<a href="https://twitter.com/arnvald">Grzegorz Witek</a>,<a href="https://twitter.com/yinquanteo">Yinquan Teo</a>,<a href="https://twitter.com/sayanee_">Sayanee Basu</a>,<a href="https://twitter.com/ntt">Chinmay Pendharkar</a>and <a href="https://twitter.com/winstonyw">Winston Teo Yong Wei</a>.We also visited Marina Sand Bay and Sentosa Island.</p><p>Back in Pune, we hosted the first ever <a href="http://www.deccanrubyconf.org/">DeccanRubyConf</a>.Our team was busy working on tasks right from building the website, inviting speakers,planning, and other arrangements. The conference had good talks and some really useful workshops.it was a fun one day conference, with attendance of over 170+ people.</p><p>The conference also saw our team announce the launch of RubyIndia Podcast,which does regular podcast interviews with notable people from the Ruby Community andIndian Community.</p><p><a href="https://bigbinary.com/team">Prathamesh</a> and I, then left on around a one and a half monthtravel, to attend and speak at multiple conferences.</p><p>We started with <a href="http://madisonpl.us/">Madison+Ruby</a>, in Madison, WI. After severalmissed flights, and a storm, we visited our first US conference after a travel of 48hours. MadisonRuby was a conference like no other. Several topics touched the humaneside of Ruby and the community. We spoke on 'Building an own ORM using ARel'. Set in thecultural town of Madison, we immensely enjoyed the cheese-curds, farmers markets and game nightarranged by the Conf team. A huge thanks to <a href="https://twitter.com/jremsikjr">Jim</a> and<a href="https://twitter.com/JenRemsik">Jennifer Remsik</a>,for hosting such an amazing event. Thanks also to <a href="https://twitter.com/ruttencutter">Scott Ruttencutter</a>for giving us space to work from his office and giving us a tour of the state capital.</p><p>We then visited Sao Paulo, Brazil for <a href="http://rubyconf.com.br">RubyConf Brazil</a> and presented a talkon Building an ORM using ARel. It was a pleasure to meet <a href="https://twitter.com/AkitaOnRails">Fabio Akita</a>and the CodeMiner team. We made friends with <a href="https://twitter.com/celsovjf">Celso Fernandes</a>and <a href="https://twitter.com/plribeiro3000">Paulo</a>who were kind enough to help us around, since in Brazil primarilyportuguese is spoken. We also met <a href="https://twitter.com/rafaelfranca">Rafael Franca</a> and<a href="https://twitter.com/cantoniodasilva">Carlos Antonio da Silva</a>who have helped us a lot with Rails issue tracker.</p><p>Next Prathamesh headed to <a href="http://rubykaigi.org">RubyKaigi</a>, being held in Tokyo, Japan.He presented on<a href="tricks-and-tips-for-using-fixtures-in-rails">Fixtures in Rails</a>.He met Matz, creator of Ruby on his first day in Japan. Mostly all thecore Ruby contributors attended RubyKaigi. He got to interact withKoichi Sasada San, <a href="https://twitter.com/1337807">Jonan Scheffler</a>,<a href="https://twitter.com/a_matsuda">Akira Matsuda</a>,<a href="https://twitter.com/chancancode">Godfrey Chan</a>,<a href="https://twitter.com/schneems">Richard Schneeman</a> and lot of awesome Rubyists.He also met with his JRuby Core <a href="https://twitter.com/tom_enebo">Tom Enebo</a> for the first time.Thanks to <a href="https://twitter.com/yahonda">Yasuo Honda</a>, <a href="https://twitter.com/mreinsch">Michael Reinsch</a> for helping with Japanese food.</p><p>From Brazil, I first visited Miami, and was happy to visit <a href="http://thelabmiami.com/">The Lab Miami</a>,<a href="http://wyncode.co/">WynCode</a> and interact with Rubyists from Miami.</p><p>Before heading to San Francisco, for GoGaRuco, I was able to make a quick stop in Boston and visit<a href="http://www.alterconf.com/sessions/boston-ma">AlterConf Boston</a>. The theme of the conference was arounddiversity in tech and gaming industry.</p><p>My latest conference was in the amazing city on San Francisco. I presented about 'Building an ORM',at <a href="http://gogaruco.com">GoGaRuCo</a>, which incidentally was the last ever GoGaRuCo. The conferencetaking place in San Francisco, saw an amazing turnout of crowd. I was able to interact with<a href="https://twitter.com/ultrasaurus">Sarah Allen</a>, <a href="https://twitter.com/wycats">Yehuda Katz</a>,<a href="https://twitter.com/pat">Pat Allen</a>, <a href="https://twitter.com/sarahmei">Sarah Mei</a>,<a href="https://twitter.com/the_zenspider">Ryan Davis</a>. I spent most of the time along with<a href="https://twitter.com/sleeplessgeek">Nathan Long</a>, <a href="https://twitter.com/randycoulman">Randy Coulman</a>,and Nathan's friend <a href="http://sorryrobot.com/">Michael Gundlach</a>,who is the creator of popularplugin <a href="https://getadblock.com">Adblock</a>.I also ran into <a href="https://twitter.com/chriseppstein">Chris Eppstein</a>, creator of <a href="http://compass-style.org">compass</a>.All around it was one of the most amazing interactions I had in a conference.</p><p>2014, was an amazing year for our team. Together we presented or were part of 8 conferences,launched RubyIndia Newsletter as well as the RubyIndia Podcast, started with 6 new videoseries on topics from <a href="https://www.bigbinary.com/videos/learn-reactjs-in-steps">ReactJS</a> to<a href="https://www.bigbinary.com/videos/learn-rubymotion">Rubymotion</a> to<a href="https://www.bigbinary.com/videos/learn-selenium">Selenium</a>, published <a href="/blog">numerous blogs</a>,and contributed to a number of OpenSource projects.</p><p>2015, starts with our team presenting at <a href="http://gardencityruby.org">GardenCityRuby Conf</a>.We hope to get more such chances to interact and help out the community. Onwards to a new year!</p>]]></content>
    </entry><entry>
       <title><![CDATA[Selenium IDE - Reducing time from 58 min to 15 min]]></title>
       <author><name>Prabhakar Battula</name></author>
      <link href="https://www.bigbinary.com/blog/how-i-reduced-selenium-test-run-time-from-58-minutes-to-15-minutes"/>
      <updated>2014-09-08T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-i-reduced-selenium-test-run-time-from-58-minutes-to-15-minutes</id>
      <content type="html"><![CDATA[<p>I wrote a bunch of selenium tests using<a href="http://www.seleniumhq.org/download">Selenium IDE</a> for a project. The seleniumtests have proven to be very useful. However the tests take around 58 minutes tocomplete the full run.</p><p>Here are the specific steps I took which brought the running time to under 15minutes.</p><h2>Set to run at the maximum speed</h2><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command &lt;/td&gt;&lt;td&gt; Target &lt;/td&gt;&lt;td&gt; Value &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;setSpeed &lt;/td&gt;&lt;td&gt;0 &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p><code>setSpeed</code> command takes <code>Target</code> value in milliseconds. By setting the value tozero, I set the speed to maximum and the tests indeed ran fast. However, now Ihad lots of tests failing which were previously passing.</p><p>What happened.</p><p>In our tests real firefox browser is fired up and real elements are clicked. Theapplication does make round trip to the rails server hosted on heroku.</p><p>By setting the selenium tests to the maximum speed the selenium tests startedasserting for elements on the page even before the pages were fully loaded bythe browser.</p><p>I needed sets of instructions using which I could tell selenium how long to waitfor before asserting for elements.</p><p>Selenium provides a wonderful suite of commands which helped me fine tune thetest run. Here I'm discussing some of those commands.</p><h2>waitForVisible</h2><p>This command is used to tell selenium to wait until the specified element isvisible on the page.</p><p>In the below mentioned case, the Selenium IDE will wait until the element<code>css=#text-a</code> is visible on the page.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command&lt;/td&gt;&lt;td&gt; Target&lt;/td&gt;&lt;td&gt; Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;waitForVisible&lt;/td&gt;&lt;td&gt;css=#text-a&lt;/td&gt;&lt;td&gt; &lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><h2>waitForText</h2><p>This command is used to tell selenium to wait until a particular text is visiblein the specified element.</p><p>In the case mentioned below, Selenium IDE will wait until the text <code>violet</code> isdisplayed in the element <code>css=#text-a</code>.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command&lt;/td&gt;&lt;td&gt; Target&lt;/td&gt;&lt;td&gt; Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;waitForText&lt;/td&gt;&lt;td&gt;css=#text-a&lt;/td&gt;&lt;td&gt;violet&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p>&lt;br /&gt;</p><p>The difference between <em>waitForVisible</em> and <em>waitForText</em> is that<strong>waitForVisible waits until the specified element is visible on the page</strong>while <strong>waitForText waits until a particular text is visible in the specifiedelement on the page</strong>.</p><h2>waitForElementPresent</h2><p>This command is used to tell Selenium to wait until the specified element isdisplayed on the page.</p><p>In the below mentioned case, the Selenium IDE will wait until the element<code>css=a.button</code> is displayed on the page.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command&lt;/td&gt;&lt;td&gt; Target&lt;/td&gt;&lt;td&gt; Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;waitForElementPresent&lt;/td&gt;&lt;td&gt;css=a.button&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p><em>waitForVisible</em> and <em>waitForElementPresent</em> seem very similar. It seems both ofthese commands do the same thing. There is a subtle difference though.</p><p><em>waitForVisible</em> waits until the specified element is visible. Visibility of anelement is manipulated by the settings of CSS properties. For example using<code>display none;</code> one can make an element not be visible at all.</p><p>In contrast the command <em>waitForElementPresent</em> waits until the specifiedelement is present on the page in the form of html markup. This command does notgive consideration to css settings.</p><h2>refreshAndWait</h2><p>This command is used to tell Selenium to wait until the page is refreshed andthe targeted element is displayed on the web page.</p><p>In the example mentioned below, the Selenium IDE will wait until the page isrefreshed and the targeted element <code>css=span.button</code> is displayed on the page.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command&lt;/td&gt;&lt;td&gt; Target&lt;/td&gt;&lt;td&gt; Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;refreshAndWait&lt;/td&gt;&lt;td&gt;css=span.button&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><h2>clickAndWait</h2><p>This command is used to tell selenium to wait until a particular button isclicked for submitting the form and the page starts reloading. The subsequentcommands are paused until, the page is reloaded after the element is clicked onthe page.</p><p>In the case mentioned below, Selenium IDE will wait until the page is reloadedafter the specified element <code>css=input#edit</code> is clicked.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Command&lt;/td&gt;&lt;td&gt; Target&lt;/td&gt;&lt;td&gt; Value&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;clickAndWait&lt;/td&gt;&lt;td&gt;css=input#edit&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p>Selenium IDE commands used above and more are available at<a href="http://docs.seleniumhq.org/docs/02_selenium_ide.jsp#selenium-commands-selenese">Selenium documentation</a>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Do not allow force push to master]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/do-not-allow-force-push-to-master"/>
      <updated>2013-09-19T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/do-not-allow-force-push-to-master</id>
      <content type="html"><![CDATA[<p>At BigBinary we create a branch for every issue. We deploy that branch and onlywhen it is approved that branch is merged into master.</p><p>Time to time we rebase the branch. And after rebasing we need to do <code>force</code> pushto send the changes to github. And once in a while someone <code>force</code> pushes intomaster by mistake. We recommend to set <a href="/how-we-work">push.default to current</a>to avoid such issues but still sometimes force push does happen in master.</p><p>In order to prevent such mistakes in future we are using<a href="https://github.com/bigbinary/tiny_scripts/blob/master/git-hooks/hooks/pre-push">pre-push hook</a>.This is a small ruby program which runs before any <code>git push</code> command. If youare force pushing to <code>master</code> then it will reject the push like this.</p><pre><code class="language-plaintext">*************************************************************************Your attempt to FORCE PUSH to MASTER has been rejected.If you still want to FORCE PUSH then you need to ignore the pre_push git hook by executing following command.git push master --force --no-verify*************************************************************************</code></pre><h2>Requirements</h2><p><code>pre-push</code> hook was<a href="https://github.com/git/git/blob/master/Documentation/RelNotes/1.8.2.txt#L167">added to git</a>in version 1.8.2. So you need git 1.8.2 or higher. You can easily upgrade git byexecuting <code>brew upgrade git</code> .</p><pre><code class="language-plaintext">$ git --versiongit version 1.8.2.3</code></pre><h2>Setting up hooks</h2><p>In order for these hooks to kick in they need to be setup.</p><p>First step is to clone the <a href="https://github.com/bigbinary/tiny_scripts">repo</a> toyour local machine. Now open <code>~/.gitconfig</code> and add following line.</p><pre><code class="language-plaintext">[init]  templatedir= /Users/neeraj/code/tiny_scripts/git-hooks</code></pre><p>Change the value <code>/Users/neeraj/code/tiny_scripts/git-hooks</code> to match with thedirectory of your machine.</p><h2>Making existing repositories aware of this hook</h2><p>Now <code>pre-push</code> hook is setup. Any new repository that you clone will have thefeature of not being able to force push to master.</p><p>But existing repositories do not know about this git-hook. To make existingrepositories aware of this hook execute following command on all repositories.</p><pre><code class="language-plaintext">$ git initReinitialized existing Git repository in /Users/nsingh/dev/projects/streetcommerce/.git/</code></pre><p>Now if you look into the <code>.git/hooks</code> directory of your project you should see afile called <code>pre-push</code>.</p><pre><code class="language-plaintext">$ ls .git/hooks/pre-push.git/hooks/pre-push</code></pre><p>It means this project is all set with <code>pre-push</code> hook.</p><h2>New repositories</h2><p>When you clone a repository then <code>git init</code> is invoked automatically and youwill get <code>pre-push</code> already copied for you. So you are all set for all futurerepositories too.</p><h2>How to ignore pre-push hook</h2><p>To ignore <code>pre-push</code> hook all you need to do is</p><pre><code class="language-plaintext"># Use following command to ignore pre-push check and to force update master.git push master --force --no-verify</code></pre><p><a href="https://github.com/bigbinary/tiny_scripts/blob/master/git-hooks/hooks/pre-push">The hook is here</a>.</p>]]></content>
    </entry><entry>
       <title><![CDATA[How to keep your fork up-to-date]]></title>
       <author><name>Neeraj Singh</name></author>
      <link href="https://www.bigbinary.com/blog/how-to-keep-your-fork-uptodate"/>
      <updated>2013-09-13T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-to-keep-your-fork-uptodate</id>
      <content type="html"><![CDATA[<p>Let's say that I'm forking repo <code>rails/rails</code>. After the repo has been forked tomy repository I will clone it on my local machine.</p><pre><code class="language-plaintext">git clone git@github.com:neerajsingh0101/rails.git</code></pre><p>Now <code>cd rails</code> and execute <code>git remote -v</code> . This is what I see.</p><pre><code class="language-plaintext">origin git@github.com:neerajsingh0101/rails.git (fetch)origin git@github.com:neerajsingh0101/rails.git (push)</code></pre><p>Now I will add <code>upstream remote</code> by executing following command.</p><pre><code class="language-plaintext">git remote add upstream git@github.com/rails/rails.git</code></pre><p>After having done that when I execute <code>git remote -v</code> then I see</p><pre><code class="language-plaintext">origin git@github.com:neerajsingh0101/rails.git (fetch)origin git@github.com:neerajsingh0101/rails.git (push)upstream git://github.com/rails/rails.git (fetch)upstream git://github.com/rails/rails.git (push)</code></pre><p>Now I want to make some changes to the code. After all this is why I forked therepo.</p><p>Let's say that I want to add exception handling to the forked code I havelocally. Then I create a branch called <code>exception-handling</code> and make all yourchanges in this branch. <strong>The key here is to not to make any changes to <code>master</code>branch</strong>. I try to keep master of my forked repository in sync with the masterof the original repository where I forked it.</p><p>So now let's create a branch and I will put in all my changes there.</p><pre><code class="language-plaintext">git checkout -b exception-handling</code></pre><p>In the <code>Gemfile</code> I will use this code like this</p><pre><code class="language-plaintext">gem 'rails', github: 'neerajsingh0101/rails', branch: 'exception-handling'</code></pre><p>A month has passed. In the meantime rails master has tons of changes. I wantthose changes in my <code>exception-handling</code> branch. In order to achieve that firstI need to bring my local master up-to-date with rails master.</p><p>I need to switch to master branch and then I need to execute following commands.</p><pre><code class="language-plaintext">git checkout mastergit fetch upstreamgit rebase upstream/mastergit push</code></pre><p>Now the master of forked repository is in-sync with the master of <code>rails/rails</code>.Now that master is up-to-date I need to pull in the changes in master in my<code>exception-handling</code> branch.</p><pre><code class="language-plaintext">git checkout exception-handlinggit rebase mastergit push -f</code></pre><p>Now my branch <code>exception-handling</code> has my fix on top of rails master.</p>]]></content>
    </entry>
     </feed>