<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vishal</title>
    <description>The latest articles on DEV Community by Vishal (@statsinsta).</description>
    <link>https://dev.to/statsinsta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4185633%2Fc2245336-6572-44c0-97e6-d9251104087a.png</url>
      <title>DEV Community: Vishal</title>
      <link>https://dev.to/statsinsta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/statsinsta"/>
    <language>en</language>
    <item>
      <title>How I Built a 100% In-Browser Instagram Chat Analyzer (Zero Cloud Uploads)</title>
      <dc:creator>Vishal</dc:creator>
      <pubDate>Wed, 14 Oct 2026 23:30:17 +0000</pubDate>
      <link>https://dev.to/statsinsta/how-i-built-a-100-in-browser-instagram-chat-analyzer-zero-cloud-uploads-1odf</link>
      <guid>https://dev.to/statsinsta/how-i-built-a-100-in-browser-instagram-chat-analyzer-zero-cloud-uploads-1odf</guid>
      <description>&lt;p&gt;Direct messages contain our most intimate conversations, jokes, memories, and photos. Yet, when searching for tools to visualize chat history or generate Spotify Wrapped-style stats for group chats, virtually every existing tool asks for your Instagram login or forces you to upload your sensitive ZIP archives to their cloud servers.&lt;/p&gt;

&lt;p&gt;I wanted to change that. I built &lt;strong&gt;&lt;a href="https://stats-insta.vercel.app/" rel="noopener noreferrer"&gt;Stats Insta&lt;/a&gt;&lt;/strong&gt; — a completely free, 100% client-side Instagram Chat Analyzer where your data never leaves your device.&lt;/p&gt;

&lt;p&gt;In this article, I will share the engineering challenges behind parsing multi-megabyte JSON exports in the browser, decoding escaped Unicode emojis, computing deep conversational metrics, and generating 9:16 Instagram Story cards directly from the DOM.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔒 The Architecture: 100% Client-Side Privacy
&lt;/h2&gt;

&lt;p&gt;The core philosophy behind &lt;strong&gt;&lt;a href="https://stats-insta.vercel.app/" rel="noopener noreferrer"&gt;Stats Insta&lt;/a&gt;&lt;/strong&gt; is &lt;strong&gt;Local-First Processing&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero Server Uploads:&lt;/strong&gt; The application does not have a backend parsing server or database for chat messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native HTML5 FileReader:&lt;/strong&gt; When a user drops a &lt;code&gt;message_1.json&lt;/code&gt; file, the browser reads the file directly into client-side RAM using &lt;code&gt;FileReader.readAsText()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session-Only Memory:&lt;/strong&gt; Once you refresh or close the browser tab, the data is instantly garbage-collected by the browser engine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can verify this in Chrome DevTools (&lt;strong&gt;F12 &amp;gt; Network Tab&lt;/strong&gt;): dropping a 50MB conversation file triggers &lt;strong&gt;0 network POST requests&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Technical Challenge 1: Decoding Instagram's Unicode Escapes
&lt;/h2&gt;

&lt;p&gt;When Meta exports your message history in JSON format, emojis and special characters are not stored as standard UTF-8 strings. Instead, they are encoded as escaped byte sequences (e.g., &lt;code&gt;\u00e2\u009d\u00a4&lt;/code&gt; for a red heart).&lt;/p&gt;

&lt;p&gt;If rendered directly in React, emojis appear as garbled characters (Mojibake). To resolve this, I implemented an in-memory Unicode normalization pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodeUnicode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unicodeString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;unicodeString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;u&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]{4})&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;%u$1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;unicodeString&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts raw escaped sequences into native browser-rendered emojis at runtime without lagging the main thread.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Technical Challenge 2: Calculating Deep Conversational Metrics
&lt;/h2&gt;

&lt;p&gt;Simple message counts are boring. People care about the hidden dynamics of their friendships. We compute several unique behavioral metrics in browser memory:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The "Night Owls" Tracker (12:00 AM – 4:59 AM)
&lt;/h3&gt;

&lt;p&gt;We parse the Unix millisecond timestamps (&lt;code&gt;timestamp_ms&lt;/code&gt;) on each message and isolate activity occurring in the late-night window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp_ms&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getHours&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;nightOwlCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nightOwlCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately reveals who stays awake sending reels and memes while everyone else is asleep.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Conversation Starter Algorithm
&lt;/h3&gt;

&lt;p&gt;Who puts in the effort to keep the conversation alive? We sort the timeline chronologically and scan for silence gaps exceeding 12 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TWELVE_HOURS_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastTimestamp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp_ms&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastTimestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TWELVE_HOURS_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;starterCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;starterCounts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sender_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;lastTimestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp_ms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. The Swear Jar
&lt;/h3&gt;

&lt;p&gt;We scan message strings against a local tokenized dictionary of 100+ global curse words and slang to generate a friendly group chat leaderboard of who has the dirtiest mouth.&lt;/p&gt;




&lt;h2&gt;
  
  
  📱 Technical Challenge 3: Generating 9:16 Instagram Story Wrapped Cards
&lt;/h2&gt;

&lt;p&gt;People love sharing aesthetic visual summaries with their friends. To make the tool virally shareable, I built an in-browser Story Card generator that renders a &lt;strong&gt;1080x1920 (9:16 Full HD)&lt;/strong&gt; graphic using HTML5 Canvas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Glassmorphism gradient backgrounds with radial glowing orbs&lt;/li&gt;
&lt;li&gt;Rounded progress bars showing conversational dominance percentages&lt;/li&gt;
&lt;li&gt;Dynamic badges and highlights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1-Click Native Share&lt;/strong&gt;: On mobile devices, we invoke the &lt;strong&gt;Web Share API&lt;/strong&gt; (&lt;code&gt;navigator.share({ files: [pngFile] })&lt;/code&gt;), which opens the native iOS/Android share sheet directly into Instagram Stories!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Try It Live &amp;amp; Explore the Code
&lt;/h2&gt;

&lt;p&gt;The project is completely free to use, and I have published detailed guides on understanding Meta's export architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 &lt;strong&gt;Live Application:&lt;/strong&gt; &lt;a href="https://stats-insta.vercel.app/" rel="noopener noreferrer"&gt;https://stats-insta.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📘 &lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://stats-insta.vercel.app/guides/read-message-json" rel="noopener noreferrer"&gt;How to Open &amp;amp; Read Instagram message_1.json Files&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📙 &lt;strong&gt;Guide:&lt;/strong&gt; &lt;a href="https://stats-insta.vercel.app/guides/most-active-hours-instagram" rel="noopener noreferrer"&gt;Activity Heatmaps &amp;amp; Peak Texting Hours&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I would love to hear feedback from the DEV community! What other client-side analytics or visual metrics would you like to see added?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
