<?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>Forem: Jay Malli</title>
    <description>The latest articles on Forem by Jay Malli (@jaymalli_programmer).</description>
    <link>https://forem.com/jaymalli_programmer</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1152524%2F228a3a43-c55a-4a24-93e7-fa6ce132b6d9.jpg</url>
      <title>Forem: Jay Malli</title>
      <link>https://forem.com/jaymalli_programmer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jaymalli_programmer"/>
    <language>en</language>
    <item>
      <title>Building Text Annotation Chrome Extensions with XPath Selection Tracking</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Mon, 29 Dec 2025 17:07:06 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/building-text-annotation-chrome-extensions-with-xpath-selection-tracking-16g0</link>
      <guid>https://forem.com/jaymalli_programmer/building-text-annotation-chrome-extensions-with-xpath-selection-tracking-16g0</guid>
      <description>&lt;p&gt;Ever tried to build a highlighter extension and hit this problem: &lt;strong&gt;"How do I remember where the user highlighted text?"&lt;/strong&gt; 🤔&lt;/p&gt;

&lt;p&gt;Saving &lt;code&gt;"Hello World"&lt;/code&gt; isn't enough — the same text might appear 100 times on a page. You need coordinates in the DOM, and those coordinates need to survive page reloads. 💾&lt;/p&gt;

&lt;p&gt;XPath is the answer. Let me show you how to build a production-ready annotation system. ✨&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 The Challenge: Text Has No Permanent Address
&lt;/h2&gt;

&lt;p&gt;Think of a webpage like a huge book 📚. When you highlight text, you can't just save &lt;em&gt;"page 47"&lt;/em&gt; — what if someone adds a new chapter? Page numbers shift!&lt;/p&gt;

&lt;h3&gt;
  
  
  What You Actually Need:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📍 &lt;strong&gt;Start position&lt;/strong&gt; - where the selection begins&lt;/li&gt;
&lt;li&gt;📍 &lt;strong&gt;End position&lt;/strong&gt; - where it ends
&lt;/li&gt;
&lt;li&gt;🌳 &lt;strong&gt;Common ancestor&lt;/strong&gt; - the closest parent containing both points&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Storage format&lt;/strong&gt; - that survives page changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; CSS selectors and element IDs change. Text content moves. How do you create a "bookmark" that works reliably? 🎯&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What We're Building
&lt;/h2&gt;

&lt;p&gt;A Chrome extension that does all this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Lets users highlight any text on any page&lt;/li&gt;
&lt;li&gt;✅ Stores highlights using stable XPaths&lt;/li&gt;
&lt;li&gt;✅ Restores highlights on page reload&lt;/li&gt;
&lt;li&gt;✅ Works across different visits&lt;/li&gt;
&lt;li&gt;✅ Supports notes and multiple colors&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🏗️ Architecture Overview:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User selects text with mouse 🖱️
      ↓
Generate XPath coordinates (start, end, ancestor) 📍
      ↓
Store in chrome.storage with offset info 💾
      ↓
On page load, read storage 📖
      ↓
Resolve XPaths → Recreate selection → Apply highlight 🎨
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📦 Setting Up the Extension
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📄 manifest.json
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Smart Highlighter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Highlight text with persistent XPath storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"highlights.css"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"run_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document_idle"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"popup.html"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎨 highlights.css
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.xpath-highlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ffeb3b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;background-color&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xpath-highlight&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xpath-highlight-note&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#4caf50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.xpath-highlight-important&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;244&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#f44336&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;h2&gt;
  
  
  ✨ Core Feature: Capturing Text Selections
&lt;/h2&gt;

&lt;p&gt;This is where the magic happens! When users select text, we capture the exact DOM coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;getXPathForSelection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getXPathForNode&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom-xpath-toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Listen for text selection&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleTextSelection&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleTextSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Ignore if clicking on existing highlight&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xpath-highlight&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="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Need actual text selected&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isCollapsed&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="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&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;selectedText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Too short to be meaningful&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="c1"&gt;// This is the magic! ✨&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getXPathForSelection&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Could not capture selection data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Captured selection:&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="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;endXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;endOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endOffset&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Store the highlight&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveHighlight&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generateId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;selectionData&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Apply visual highlight immediately&lt;/span&gt;
    &lt;span class="nf"&gt;applyHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectionData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xpath-highlight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Clear selection&lt;/span&gt;
    &lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeAllRanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Highlight saved!&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Failed to save highlight:&lt;/span&gt;&lt;span class="dl"&gt;'&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="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Failed to save highlight&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateId&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="s2"&gt;`highlight_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&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;h2&gt;
  
  
  🗺️ Understanding the Selection Data
&lt;/h2&gt;

&lt;p&gt;Let's break down what &lt;code&gt;getXPathForSelection()&lt;/code&gt; returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;startXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@id="content"]/p[1]/text()[1]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 📍 Where selection starts&lt;/span&gt;
  &lt;span class="nx"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                   &lt;span class="c1"&gt;// 🔢 Character position in start node&lt;/span&gt;
  &lt;span class="nx"&gt;endXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@id="content"]/p[1]/text()[1]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 📍 Where selection ends&lt;/span&gt;
  &lt;span class="nx"&gt;endOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                                     &lt;span class="c1"&gt;// 🔢 Character position in end node&lt;/span&gt;
  &lt;span class="nx"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@id="content"]/p[1]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// 🌳 Closest common parent&lt;/span&gt;
  &lt;span class="nx"&gt;selectedText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;this is the selected text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;         &lt;span class="c1"&gt;// 📝 The actual text&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🎯 Think of it like GPS coordinates:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XPath&lt;/strong&gt; = Street address 🏠&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offset&lt;/strong&gt; = House number on that street 🔢&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ancestor&lt;/strong&gt; = The neighborhood 🏘️&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination gives you an exact, reproducible location in the DOM! 📍&lt;/p&gt;

&lt;h2&gt;
  
  
  💾 Storing Highlights Persistently
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Get existing highlights for this URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`highlights_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&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="c1"&gt;// Add new highlight&lt;/span&gt;
  &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Save back to storage&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`💾 Saved highlight &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Create URL hash for storage key&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Simple hash function&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;hash&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="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;char&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;hash&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&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;&lt;strong&gt;💡 Why hash the URL?&lt;/strong&gt; Chrome storage keys have length limits. Hashing keeps keys short and consistent! 🔑&lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 Restoring Highlights on Page Load
&lt;/h2&gt;

&lt;p&gt;This is where XPath shines! ✨ We can recreate the exact selection from storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run on page load&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;restoreHighlights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`highlights_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`🔄 Restoring &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; highlights...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlight&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&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="nf"&gt;applyHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xpath-highlight&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`⚠️ Failed to restore highlight &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&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="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Highlights restored!&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="c1"&gt;// Wait for DOM to be ready&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loading&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;restoreHighlights&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;restoreHighlights&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;h2&gt;
  
  
  🎨 The Magic: Applying Highlights with XPath
&lt;/h2&gt;

&lt;p&gt;Here's how we turn XPath coordinates back into visual highlights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endOffset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlightData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Resolve XPaths to actual DOM nodes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startXPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endXPath&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;startNode&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;endNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Could not resolve XPath nodes&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="c1"&gt;// Create a Range (native browser API)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRange&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="c1"&gt;// Set range boundaries using XPath nodes + offsets&lt;/span&gt;
    &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startOffset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endOffset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Wrap selection in a &amp;lt;mark&amp;gt; element&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlightData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Surround the range with our mark element&lt;/span&gt;
    &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;surroundContents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Add click handler for notes/deletion&lt;/span&gt;
    &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;handleHighlightClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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="c1"&gt;// Fallback for complex selections spanning multiple nodes&lt;/span&gt;
    &lt;span class="nf"&gt;applyHighlightComplex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;highlightData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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="c1"&gt;// Fallback for selections that span multiple nodes&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyHighlightComplex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Extract contents&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractContents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Wrap in mark&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Insert back&lt;/span&gt;
  &lt;span class="nx"&gt;range&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mark&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;&lt;strong&gt;🎯 Key concept:&lt;/strong&gt; We use the browser's native &lt;code&gt;Range&lt;/code&gt; API with XPath-resolved nodes to recreate the exact selection! &lt;/p&gt;

&lt;h2&gt;
  
  
  📝 Adding Note-Taking Features
&lt;/h2&gt;

&lt;p&gt;Let's make highlights interactive with right-click menus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleHighlightClick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContextMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createContextMenu&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;highlight-menu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cssText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    position: fixed;
    left: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px;
    top: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;px;
    background: white;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    z-index: 10000;
    padding: 8px;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;button class="menu-btn" data-action="note"&amp;gt;📝 Add Note&amp;lt;/button&amp;gt;
    &amp;lt;button class="menu-btn" data-action="color"&amp;gt;🎨 Change Color&amp;lt;/button&amp;gt;
    &amp;lt;button class="menu-btn" data-action="delete"&amp;gt;🗑️ Delete&amp;lt;/button&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;addNoteToHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;changeHighlightColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;deleteHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Close menu when clicking elsewhere&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&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;menu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;addNoteToHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;📝 Add a note to this highlight:&lt;/span&gt;&lt;span class="dl"&gt;'&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`highlights_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;highlightId&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;highlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Update UI&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-highlight-id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&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;mark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;has-note&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Note added!&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;deleteHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;)&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="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🗑️ Delete this highlight?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`highlights_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&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="c1"&gt;// Remove from storage&lt;/span&gt;
  &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Remove from DOM&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[data-highlight-id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;highlightId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"]`&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;mark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;showToast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Highlight deleted&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🎨 Building the Popup UI
&lt;/h2&gt;

&lt;p&gt;Let users manage all their highlights from the extension popup:&lt;/p&gt;

&lt;h3&gt;
  
  
  📄 popup.html
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;350px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.highlight-item&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ffeb3b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="m"&gt;0.2s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.highlight-item&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.highlight-text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;13px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nc"&gt;.highlight-meta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;11px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#666&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;#export-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#667eea&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;📚 Your Highlights&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"highlights-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"export-btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;📤 Export All&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"popup.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔧 popup.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadHighlights&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storageKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`highlights_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;hashUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;storageKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;storageKey&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;highlights-list&lt;/span&gt;&lt;span class="dl"&gt;'&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;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;p&amp;gt;📝 No highlights on this page yet.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`
    &amp;lt;div class="highlight-item" data-id="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
      &amp;lt;div class="highlight-text"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;/div&amp;gt;
      &amp;lt;div class="highlight-meta"&amp;gt;
        📅 &lt;/span&gt;&lt;span class="p"&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;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`• 📝 &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;export-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Export all highlights as JSON&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&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;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;highlights-export.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Highlights exported!&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="nf"&gt;loadHighlights&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚡ Performance Optimization Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ Lazy Restoration
&lt;/h3&gt;

&lt;p&gt;Don't restore all highlights immediately—load visible ones first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;restoreHighlightsLazy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getHighlightsForPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Restore above-the-fold immediately&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isInViewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xpath-highlight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Restore others on scroll&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isInViewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scroll&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isInViewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ancestorXPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;applyHighlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xpath-highlight&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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isInViewport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xpath&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBoundingClientRect&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;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHeight&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bottom&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="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;&lt;strong&gt;💡 Pro tip:&lt;/strong&gt; Only render what users can see! This dramatically improves performance on pages with many highlights. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 Resources &amp;amp; Next Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  📦 Tools &amp;amp; Documentation:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/JayMalli/dom-xpath-toolkit" rel="noopener noreferrer"&gt;GitHub: dom-xpath-toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/dom-xpath-toolkit" rel="noopener noreferrer"&gt;npm: dom-xpath-toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Selection" rel="noopener noreferrer"&gt;Selection API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Range" rel="noopener noreferrer"&gt;Range API Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Let's Connect!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Built something cool with text selection?&lt;/strong&gt; Share it in the comments below! I'd love to see what you're creating. 👇&lt;/p&gt;

&lt;p&gt;Found this helpful? Connect with me on LinkedIn for more Chrome extension tips and tricks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;🔗 Find me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>chromeextension</category>
      <category>xpath</category>
      <category>webdev</category>
      <category>domxpathtoolkit</category>
    </item>
    <item>
      <title>Building Reliable Content Scripts: Why XPath Beats querySelector in Chrome Extensions</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Sun, 28 Dec 2025 10:19:01 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/building-reliable-content-scripts-why-xpath-beats-queryselector-in-chrome-extensions-14ol</link>
      <guid>https://forem.com/jaymalli_programmer/building-reliable-content-scripts-why-xpath-beats-queryselector-in-chrome-extensions-14ol</guid>
      <description>&lt;p&gt;Ever built a Chrome extension that worked perfectly... until the website updated their CSS classes? 🤦‍♂️&lt;/p&gt;

&lt;p&gt;You're not alone. CSS selectors are fragile because they depend on styling decisions that change frequently. Let me show you a better approach using XPath.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚨 The Problem: CSS Selectors Are Built for Styling, Not Stability
&lt;/h2&gt;

&lt;p&gt;Think of CSS selectors like following directions using landmarks: &lt;em&gt;"Turn left at the blue house"&lt;/em&gt;. What happens when someone paints the house red? Your directions break. 🏠🎨&lt;/p&gt;

&lt;p&gt;Here's a real example I've seen break in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fragile approach&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn.btn-primary.submit-action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this breaks:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🎨 Marketing team changes &lt;code&gt;btn-primary&lt;/code&gt; to &lt;code&gt;btn-brand&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🔧 Developer refactors CSS to use utility classes&lt;/li&gt;
&lt;li&gt;🧪 A/B test adds &lt;code&gt;.experiment-variant-b&lt;/code&gt; class&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;💥 Your extension stops working&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ The Solution: XPath with Smart Heuristics
&lt;/h2&gt;

&lt;p&gt;XPath is like having a GPS coordinate system for your DOM. Instead of relying on cosmetic classes, it uses &lt;strong&gt;stable identifiers&lt;/strong&gt; that websites rarely change. 🎯&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Stable approach&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getXPathForNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolveXPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom-xpath-toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Generate stable XPath&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid="submit-form"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stableXPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getXPathForNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submitButton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //*[@data-testid="submit-form"]&lt;/span&gt;

&lt;span class="c1"&gt;// Later, reliably find it again&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@data-testid="submit-form"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛠️ Real-World Example: Building a Form Auto-Filler
&lt;/h2&gt;

&lt;p&gt;Let's build a content script that automatically fills login forms. I'll show both approaches so you can see the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ The Fragile CSS Way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// manifest.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest_version&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content_scripts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;matches&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;js&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content.js&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="c1"&gt;// content.js - CSS approach&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;autofillLogin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.input-field.email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passwordField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.form-control[type="password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn.submit&lt;/span&gt;&lt;span class="dl"&gt;'&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;emailField&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;passwordField&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;submitBtn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form elements not found!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// This happens ALL THE TIME&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;emailField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;passwordField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;securepassword123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;submitBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;autofillLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  ⚠️ What breaks this:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;CSS framework migration (Bootstrap → Tailwind)&lt;/li&gt;
&lt;li&gt;Class name obfuscation in production builds&lt;/li&gt;
&lt;li&gt;Component library updates&lt;/li&gt;
&lt;li&gt;Dynamic class generation from CSS-in-JS&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ✅ The Stable XPath Way
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content.js - XPath approach&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;getXPathByAttribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getXPathByLabel&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom-xpath-toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;autofillLogin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Multiple fallback strategies!&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@name="email"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@type="email"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getXPathByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passwordField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@name="password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@autocomplete="current-password"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;submitBtn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;//*[@type="submit"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nf"&gt;resolveXPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getXPathByAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;emailField&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;passwordField&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;submitBtn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Form elements not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;emailField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;passwordField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;securepassword123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;submitBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;autofillLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  💪 Why this is better:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;✅ Targets semantic HTML attributes (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;autocomplete&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ Falls back through multiple strategies&lt;/li&gt;
&lt;li&gt;✅ Works across framework rewrites&lt;/li&gt;
&lt;li&gt;✅ Survives minification and obfuscation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Stability Hierarchy: What to Target
&lt;/h2&gt;

&lt;p&gt;The toolkit uses a smart priority system. Here's what it looks for in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getXPathForNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom-xpath-toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Priority 1: ID (most stable) 🔑&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;button id="checkout-btn"&amp;gt;Buy Now&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //*[@id="checkout-btn"]&lt;/span&gt;

&lt;span class="c1"&gt;// Priority 2: data-* attributes (test IDs) 🧪&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;button data-testid="checkout"&amp;gt;Buy Now&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //*[@data-testid="checkout"]&lt;/span&gt;

&lt;span class="c1"&gt;// Priority 3: Semantic attributes 📋&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;button name="submit" type="submit"&amp;gt;Buy Now&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //button[@type="submit" and @name="submit"]&lt;/span&gt;

&lt;span class="c1"&gt;// Priority 4: ARIA attributes ♿&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;button aria-label="Checkout"&amp;gt;Buy Now&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //*[@aria-label="Checkout"]&lt;/span&gt;

&lt;span class="c1"&gt;// Last resort: Structure 🏗️&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;div&amp;gt;&amp;lt;button&amp;gt;Buy Now&amp;lt;/button&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;// Result: //div/button[contains(text(),"Buy Now")]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📦 Installation in Your Extension
&lt;/h2&gt;

&lt;p&gt;Setting up the toolkit takes 2 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dom-xpath-toolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then bundle it with your extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// content.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getXPathForNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolveXPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dom-xpath-toolkit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Now you can use it anywhere in your content scripts!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚠️ When NOT to Use XPath
&lt;/h2&gt;

&lt;p&gt;XPath isn't always the answer. Stick with CSS selectors when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ You control the HTML (your own popup/options page)&lt;/li&gt;
&lt;li&gt;✅ You need &lt;code&gt;:hover&lt;/code&gt;, &lt;code&gt;::before&lt;/code&gt; pseudo-selectors (XPath can't do these)&lt;/li&gt;
&lt;li&gt;✅ Performance on massive DOMs (CSS is slightly faster)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for content scripts interacting with third-party sites? &lt;strong&gt;XPath wins every time.&lt;/strong&gt; 🏆&lt;/p&gt;




&lt;h2&gt;
  
  
  🎮 Try It Yourself
&lt;/h2&gt;

&lt;p&gt;I've built a live playground where you can test XPath generation on real websites:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://jaymalli.github.io/dom-xpath-toolkit/" rel="noopener noreferrer"&gt;XPath Toolkit Playground&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🧪 Test different heuristic strategies&lt;/li&gt;
&lt;li&gt;📊 See stability scores for each selector&lt;/li&gt;
&lt;li&gt;📋 Copy generated XPaths directly&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📚 Quick Reference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/JayMalli/dom-xpath-toolkit" rel="noopener noreferrer"&gt;dom-xpath-toolkit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/dom-xpath-toolkit" rel="noopener noreferrer"&gt;dom-xpath-toolkit&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🤝 Let's Connect!
&lt;/h3&gt;

&lt;p&gt;If you found this guide helpful, let's keep the conversation going! I regularly post deep dives, security tips, and new projects I'm working on.&lt;/p&gt;

&lt;p&gt;Connect with me on LinkedIn—I'd love to hear about what you're building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;Find me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




</description>
      <category>chromeextension</category>
      <category>xpath</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🛠️ Build Your First Chrome Extension (with a React Bonus)</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Tue, 15 Jul 2025 21:47:30 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/build-your-first-chrome-extension-with-a-react-bonus-2af9</link>
      <guid>https://forem.com/jaymalli_programmer/build-your-first-chrome-extension-with-a-react-bonus-2af9</guid>
      <description>&lt;p&gt;Ever been browsing the web and thought, "I wish I could just add a little button here that does &lt;em&gt;this&lt;/em&gt;"? Well, you can! Chrome extensions are your gateway to customizing your browsing experience, and they're surprisingly easy to build.&lt;/p&gt;

&lt;p&gt;In this guide, we'll create a simple but powerful Chrome extension from scratch. By the end, you'll have a working extension that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎨 Change the background color of any website.&lt;/li&gt;
&lt;li&gt;🔔 Show desktop notifications.&lt;/li&gt;
&lt;li&gt;💾 Save and load data.&lt;/li&gt;
&lt;li&gt;🚀 Inject a floating button onto any page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to become a browser magician? Let's dive in!&lt;/p&gt;




&lt;h2&gt;
  
  
  📂 The File Structure
&lt;/h2&gt;

&lt;p&gt;First, create a new folder. Inside, we'll create the following files. This is the complete anatomy of our simple extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-first-extension/
├── manifest.json       # The most important file; the extension's blueprint
├── popup.html          # The UI you see when you click the icon
├── popup.js            # The logic for our popup
├── background.js       # The extension's brain, runs in the background
├── content.js          # Code that runs on the webpages you visit
├── content.css         # Styles for our code on webpages
└── icons/              # Folder for our extension's icons
    ├── icon16.png
    ├── icon32.png
    ├── icon48.png
    └── icon128.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9brpd6wm16c193sm6gy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9brpd6wm16c193sm6gy3.png" alt="Confused" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The "Restaurant" Analogy for Chrome Extensions
&lt;/h3&gt;

&lt;p&gt;To make sense of how these files work together, let's use an analogy. Think of your Chrome extension as a small, efficient restaurant operating inside the larger city of your browser.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;manifest.json&lt;/code&gt; (The Restaurant License &amp;amp; Menu):&lt;/strong&gt; This is your official business license. It tells the city (Chrome) your restaurant's name, where it is, what it needs to operate (permissions like electricity and water), and what's on the menu (the features you offer). Without it, you can't even open.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;popup.html&lt;/code&gt; &amp;amp; &lt;code&gt;popup.css&lt;/code&gt; (The Front Counter):&lt;/strong&gt; This is the customer-facing part of your restaurant. It's where customers walk up, see the daily specials, and place their orders. It needs to be clean, well-designed (&lt;code&gt;popup.css&lt;/code&gt;), and easy to understand (&lt;code&gt;popup.html&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;popup.js&lt;/code&gt; (The Cashier):&lt;/strong&gt; The cashier takes the customer's order. They listen for what the customer wants ("I'll have the 'Change Background' special!"), press the right buttons on the register, and send the order to the kitchen. They are the direct link between the customer and the back-of-house operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;background.js&lt;/code&gt; (The Head Chef &amp;amp; Kitchen):&lt;/strong&gt; This is the heart of the operation. The kitchen runs constantly, even when there are no customers at the counter. The head chef receives orders from the cashier and knows how to prepare everything on the menu (like making a "Notification"). It also handles background tasks like receiving supplies (alarms) or managing the restaurant's inventory (storage).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;content.js&lt;/code&gt; (The Waiter Delivering to a Table):&lt;/strong&gt; Sometimes, an order isn't for takeout; it's for a customer already seated in the main dining hall (a webpage). The content script is the waiter who takes the prepared dish from the kitchen, walks out into the dining hall, and delivers it directly to the customer's table, sometimes even rearranging the salt and pepper shakers on the table (modifying the DOM).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, this team creates a seamless experience, from ordering at the counter to enjoying a feature on a webpage!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwyenb6m428cq5kycej2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwyenb6m428cq5kycej2.jpg" alt="Analogy" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Blueprint: &lt;code&gt;manifest.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Every extension &lt;strong&gt;must&lt;/strong&gt; have a &lt;code&gt;manifest.json&lt;/code&gt; file. It's the control center that tells Chrome everything it needs to know about your extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My First Chrome Extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A simple Chrome extension for beginners"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"activeTab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"notifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"scripting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"alarms"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"popup.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default_icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"16"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon16.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"48"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon48.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"128"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon128.png"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;all_urls&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content.css"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"16"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon16.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"48"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon48.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"128"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icons/icon128.png"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What does this all mean?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"manifest_version": 3&lt;/code&gt;: We're using the latest and greatest Chrome extension platform version.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"permissions"&lt;/code&gt;: This is crucial. We're asking for permission to use certain Chrome APIs. We need &lt;code&gt;"scripting"&lt;/code&gt; to run code on pages, &lt;code&gt;"notifications"&lt;/code&gt; to show alerts, and so on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"action"&lt;/code&gt;: Defines what happens when you click the extension icon in the toolbar. We're telling it to open &lt;code&gt;popup.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"background"&lt;/code&gt;: Points to our background script, the extension's persistent brain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"content_scripts"&lt;/code&gt;: This is where the magic happens. We're telling Chrome to inject &lt;code&gt;content.js&lt;/code&gt; and &lt;code&gt;content.css&lt;/code&gt; into &lt;em&gt;every&lt;/em&gt; webpage (&lt;code&gt;"&amp;lt;all_urls&amp;gt;"&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. The Face: &lt;code&gt;popup.html&lt;/code&gt; &amp;amp; &lt;code&gt;popup.js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When you click your extension's icon, a small window appears. That's the popup, and it's just a tiny webpage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;popup.html&lt;/code&gt;&lt;/strong&gt; is a standard HTML file with some buttons and a status area.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;300px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* ... and other styles */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;My First Extension&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"changeBackground"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Change Page Background&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"showNotification"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Show Notification&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Ready to use!&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"popup.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;popup.js&lt;/code&gt;&lt;/strong&gt; brings the popup to life. It listens for clicks and tells other parts of the extension what to do.&lt;/p&gt;

&lt;p&gt;Here's the most interesting part—changing the page background:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In popup.js&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changeBackground&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;currentWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 💡 Pro Tip: You can't run scripts on Chrome's internal pages!&lt;/span&gt;
    &lt;span class="c1"&gt;// So we add a check to give the user a friendly message.&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;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome://&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Cannot run on Chrome pages.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scripting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeScript&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;tabId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;changePageBackground&lt;/span&gt; &lt;span class="c1"&gt;// This function is executed on the webpage&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// This function is NOT in popup.js's scope.&lt;/span&gt;
&lt;span class="c1"&gt;// It gets sent to the content script to be executed.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;changePageBackground&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;randomColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;16777215&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;randomColor&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 is a key concept: the popup is a separate world. To affect a webpage, it uses the &lt;code&gt;chrome.scripting.executeScript&lt;/code&gt; API to send a function to be executed on that page.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Engine Room: &lt;code&gt;background.js&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The background script is a &lt;strong&gt;service worker&lt;/strong&gt;. It's the extension's core, always ready to listen for events, even when the popup is closed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In background.js&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Background script loaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Listen for messages from other scripts (like the popup)&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;)&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;showNotification&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;iconUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icons/icon48.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Extension Notification&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from your extension!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Keep the message channel open for async response&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the background script listens for a message with the action &lt;code&gt;showNotification&lt;/code&gt;. Why do this here and not in the popup? Because the background script persists. It can handle tasks like notifications, alarms, or listening for tab updates, which don't depend on a UI being open.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Infiltrator: &lt;code&gt;content.js&lt;/code&gt; &amp;amp; &lt;code&gt;content.css&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The content script is your agent inside the webpage. It has access to the page's DOM, allowing it to read and modify content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In content.js&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content script loaded on:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a floating button and add it to the page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myExtensionButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🚀 My Extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-extension-floating-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello from the content script!&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script creates a button, gives it some text and a class, and appends it to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; of every page you visit.&lt;/p&gt;

&lt;p&gt;Of course, an unstyled button looks ugly. That's where &lt;strong&gt;&lt;code&gt;content.css&lt;/code&gt;&lt;/strong&gt; comes in. These styles are also injected into the page to make our button look fabulous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* In content.css */&lt;/span&gt;
&lt;span class="nc"&gt;.my-extension-floating-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#667eea&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#764ba2&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&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;h2&gt;
  
  
  🛠️ Installation &amp;amp; Debugging
&lt;/h2&gt;

&lt;p&gt;You've written the code, now let's bring it to life!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Open Chrome and navigate to &lt;code&gt;chrome://extensions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Flick the &lt;strong&gt;"Developer mode"&lt;/strong&gt; switch in the top-right corner.&lt;/li&gt;
&lt;li&gt; Click the &lt;strong&gt;"Load unpacked"&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt; Select the folder containing your extension files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your extension should now appear in your toolbar!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Houston, we have a problem."&lt;/strong&gt; How do you debug?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Popup:&lt;/strong&gt; Right-click the extension icon and select "Inspect popup". A DevTools window will open for the popup.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Background Script:&lt;/strong&gt; On the &lt;code&gt;chrome://extensions&lt;/code&gt; page, find your extension and click the "Inspect views: service worker" (or similar) link.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Content Script:&lt;/strong&gt; Open DevTools (F12) on any webpage where your script is running. Your logs will appear in that page's console, prefixed with your extension's name.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  💻 Source Code for the Simple Extension
&lt;/h3&gt;

&lt;p&gt;You can find the complete code for the vanilla JavaScript extension covered in this tutorial on GitHub.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/JayMalli/My-First-Chrome-Extension" rel="noopener noreferrer"&gt;Get the code: Simple Chrome Extension Starter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🎉 What's Next?
&lt;/h2&gt;

&lt;p&gt;You've done it! You've built a fully functional Chrome extension. You've learned about the manifest, popups, background scripts, and content scripts—the fundamental building blocks of all extensions.&lt;/p&gt;

&lt;p&gt;This is just the beginning. You can now explore other powerful Chrome APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;chrome.storage&lt;/code&gt;&lt;/strong&gt;: For more complex data storage.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;chrome.contextMenus&lt;/code&gt;&lt;/strong&gt;: Add options to the right-click menu.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;chrome.webRequest&lt;/code&gt;&lt;/strong&gt;: Intercept and modify network requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🎁 Bonus Section: Level Up with React and Webpack!
&lt;/h2&gt;

&lt;p&gt;The vanilla JavaScript approach is perfect for simple extensions, but what if you want to build something more complex? What if you love using frameworks like &lt;strong&gt;React&lt;/strong&gt; or &lt;strong&gt;Vue&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;You absolutely can! But it requires a &lt;strong&gt;build step&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Do I Need a Build Step?
&lt;/h3&gt;

&lt;p&gt;Browsers don't understand React's JSX syntax out of the box. We need a tool to convert our modern, framework-based code into the plain HTML, JavaScript, and CSS that Chrome can understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Webpack?
&lt;/h3&gt;

&lt;p&gt;Webpack is a &lt;strong&gt;module bundler&lt;/strong&gt;. It takes all your project files (React components, CSS-in-JS, different JS modules), processes them, and bundles them into a few static files that are ready for the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modern Project Structure
&lt;/h3&gt;

&lt;p&gt;Your source code would look a little different. Instead of writing directly into &lt;code&gt;popup.js&lt;/code&gt;, you'd have a &lt;code&gt;src&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-react-extension/
├── src/
│   ├── popup/
│   │   ├── Popup.jsx       # Your React component
│   │   └── index.js        # Entry point to render the component
│   ├── background/
│   │   └── index.js        # Your background script
│   └── content/
│       └── index.js        # Your content script
├── public/
│   ├── manifest.json
│   ├── popup.html          # A simple HTML template
│   └── icons/
│       └── icon128.png
└── webpack.config.js       # The magic configuration file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Webpack Configuration (&lt;code&gt;webpack.config.js&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;This file tells Webpack how to bundle everything. It looks intimidating, but it's quite logical.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HtmlWebpackPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html-webpack-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CopyWebpackPlugin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy-webpack-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Entry points: Define which files are the start of each bundle.&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;popup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/popup/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/background/index.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/content/index.js&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="c1"&gt;// 2. Output: Where to put the final bundled files.&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[name].bundle.js&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="c1"&gt;// 3. Modules &amp;amp; Rules: How to handle different file types.&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&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="na"&gt;test&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;js|jsx&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// For any .js or .jsx file...&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ...use Babel to transpile it to older JS.&lt;/span&gt;
          &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@babel/preset-react&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// 4. Plugins: Extra tools to help the build process.&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// Creates popup.html in the 'dist' folder from our template.&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HtmlWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./public/popup.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;popup.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;popup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Only include the 'popup.bundle.js' in this HTML file.&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="c1"&gt;// Copies static files (like manifest.json and icons) to the 'dist' folder.&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CopyWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;patterns&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public/manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;manifest.json&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="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public/icons&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icons&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="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Final Step: The Build
&lt;/h3&gt;

&lt;p&gt;After setting this up, you'd run a command like &lt;code&gt;npm run build&lt;/code&gt;. Webpack would then generate a &lt;code&gt;dist&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;dist&lt;/code&gt; folder contains the vanilla, browser-readable code. &lt;strong&gt;This is the folder you would load into Chrome as your "unpacked extension."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This approach lets you use all the power of modern web development to build amazing, complex, and maintainable Chrome extensions.&lt;/p&gt;




&lt;h3&gt;
  
  
  💻 Source Code for the React Extension
&lt;/h3&gt;

&lt;p&gt;Want to skip the setup and dive right into building with React? Here is a complete starter template with Webpack, React, and Chakra UI pre-configured.&amp;gt; &lt;strong&gt;[Get the code: React Chrome Extension Starter(&lt;a href="https://github.com/JayMalli/My-First-Chrome-Extension/tree/main/ReactJs_extension" rel="noopener noreferrer"&gt;https://github.com/JayMalli/My-First-Chrome-Extension/tree/main/ReactJs_extension&lt;/a&gt;)&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🤝 Let's Connect!
&lt;/h3&gt;

&lt;p&gt;If you found this guide helpful, let's keep the conversation going! I regularly post deep dives, security tips, and new projects I'm working on.&lt;/p&gt;

&lt;p&gt;Connect with me on LinkedIn—I'd love to hear about what you're building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;Find me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;What will you build next? A tool to save your favorite articles? A theme customizer for your favorite website? The possibilities are endless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Share your ideas in the comments below! Happy coding!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>chromeextension</category>
      <category>javascript</category>
      <category>react</category>
      <category>manifestv3</category>
    </item>
    <item>
      <title>🚀 Unlocking G Suite SSO in Your Chrome Extension: The Definitive Manifest V3 Guide</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Tue, 15 Jul 2025 20:38:10 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/unlocking-g-suite-sso-in-your-chrome-extension-the-definitive-manifest-v3-guide-2ghi</link>
      <guid>https://forem.com/jaymalli_programmer/unlocking-g-suite-sso-in-your-chrome-extension-the-definitive-manifest-v3-guide-2ghi</guid>
      <description>&lt;p&gt;Ever dreamed of building a Chrome extension that seamlessly integrates with a user's Google Workspace? Imagine your users, with a single click, securely logging in and unlocking a world of productivity. It sounds amazing, right?&lt;/p&gt;

&lt;p&gt;But then, you hit a wall. A big, scary, red-bricked wall called &lt;strong&gt;Manifest V3&lt;/strong&gt;. 🧱&lt;/p&gt;

&lt;p&gt;You start seeing errors that make you want to flip your desk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Refused to execute inline script because it violates the following Content Security Policy...&lt;/li&gt;
&lt;li&gt;  OAuth2 client ID is not supported...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppm0ckzl258shphfeptd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppm0ckzl258shphfeptd.png" alt="Errors" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've tried to implement Google SSO, you know the struggle. Manifest V3's strict security policies have made old methods obsolete. So, how do you navigate this new, secure world?&lt;/p&gt;

&lt;p&gt;Don't worry, we've got the map! 🗺️ This guide will walk you through the modern, secure, and Google-approved way to implement G Suite SSO in your Manifest V3 extension.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚦 Before We Dive In: A Quick Stop for Beginners
&lt;/h3&gt;

&lt;p&gt;This guide jumps right into the deep end with a specific, slightly advanced topic: G Suite SSO in Manifest V3.&lt;/p&gt;

&lt;p&gt;If you're just starting your journey and terms like &lt;code&gt;manifest.json&lt;/code&gt;, &lt;code&gt;service worker&lt;/code&gt;, or &lt;code&gt;content script&lt;/code&gt; are still new to you, I highly recommend checking out my foundational guide first. It covers the A-to-Z of building your very first extension and is the perfect starting point to get you up to speed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.toURL_TO_YOUR_BEGINNER_BLOG_POST"&gt;🛠️ Build Your First Chrome Extension (with a React Bonus)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you've got the basics down, come on back—this post will be waiting for you!&lt;/p&gt;




&lt;h3&gt;
  
  
  The Manifest V3 Challenge: Why Is It So Hard? 👹
&lt;/h3&gt;

&lt;p&gt;Google's Manifest V3 is all about making extensions safer and more private. While this is great for users, it means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;No More Remote Code:&lt;/strong&gt; You can't load scripts from external servers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Service Workers Only:&lt;/strong&gt; Background tasks run in ephemeral service workers.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Strict Content Security Policy (CSP):&lt;/strong&gt; Inline scripts and &lt;code&gt;eval()&lt;/code&gt; are forbidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes mean the old "just do a web redirect" OAuth flow is a recipe for disaster. The solution? We need to use the tool Google built specifically for this job.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hero of Our Story: The &lt;code&gt;chrome.identity&lt;/code&gt; API ✨
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjug5k7yaq55p7575i10s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjug5k7yaq55p7575i10s.png" alt="Solution" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meet the &lt;code&gt;chrome.identity&lt;/code&gt; API. This is your magic wand for handling OAuth 2.0 within a Chrome extension. It provides a secure UI prompt, managed by Chrome itself, to handle the entire authentication flow without redirects or popups that get blocked.&lt;/p&gt;

&lt;p&gt;Let's build this, step-by-step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Get Your Extension ID &amp;amp; Cloud Credentials ⚔️
&lt;/h3&gt;

&lt;p&gt;Before you can get OAuth credentials, you need the unique ID of your extension. This is a crucial step that's often missed, as Google needs to know exactly which extension is allowed to use your credentials.&lt;/p&gt;

&lt;h4&gt;
  
  
  First, Find Your Extension ID
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt; Make sure your extension is loaded in Chrome. If you haven't already, go to &lt;code&gt;chrome://extensions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Enable &lt;strong&gt;Developer mode&lt;/strong&gt; in the top right corner.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;Load unpacked&lt;/strong&gt; and select your extension's folder.&lt;/li&gt;
&lt;li&gt; Your extension will appear in the list with a unique &lt;strong&gt;ID&lt;/strong&gt; (a long string of letters).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Copy this ID!&lt;/strong&gt; You'll need it in the next part.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mb5hbrph5fzw1w7hgrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mb5hbrph5fzw1w7hgrd.png" alt="Chrome Extension unique Id" width="502" height="270"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Next, Create OAuth Credentials
&lt;/h4&gt;

&lt;p&gt;Now that you have your ID, head to the Google Cloud Platform.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to the &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Create a new project (e.g., "My Awesome Extension").&lt;/li&gt;
&lt;li&gt; Navigate to &lt;strong&gt;APIs &amp;amp; Services &amp;gt; Credentials&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;+ CREATE CREDENTIALS&lt;/strong&gt; and select &lt;strong&gt;OAuth client ID&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; For &lt;strong&gt;Application type&lt;/strong&gt;, you MUST select &lt;strong&gt;Chrome Extension&lt;/strong&gt;.
&amp;gt; &lt;strong&gt;Warning&lt;/strong&gt;
&amp;gt; This is the most common mistake—do not choose "Web application"!&lt;/li&gt;
&lt;li&gt; Give it a name (e.g., "Extension GSuite Login").&lt;/li&gt;
&lt;li&gt; In the &lt;strong&gt;Application ID/Item ID&lt;/strong&gt; field, paste the unique extension ID you just copied from &lt;code&gt;chrome://extensions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;CREATE&lt;/strong&gt;. You will now see your &lt;strong&gt;Client ID&lt;/strong&gt;. Copy this! We'll need it for the manifest file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Crafting the Perfect &lt;code&gt;manifest.json&lt;/code&gt; 📜
&lt;/h3&gt;

&lt;p&gt;Your &lt;code&gt;manifest.json&lt;/code&gt; is the blueprint. It needs to declare the right permissions and your OAuth Client ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// filepath: manifest.json
{
    "manifest_version": 3,
    "name": "G Suite SSO Login Demo",
    "version": "1.0",
    "description": "A secure way to implement G Suite SSO with Manifest V3.",
    "permissions": [
        "identity",
        "storage",
        "alarms"
    ],
    "background": {
        "service_worker": "background.js"
    },
    "action": {
        "default_popup": "popup.html"
    },
    "oauth2": {
        "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
        "scopes": [
            "https://www.googleapis.com/auth/userinfo.profile",
            "https://www.googleapis.com/auth/userinfo.email"
        ]
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 3: The Authentication Logic (&lt;code&gt;popup.js&lt;/code&gt;) 🧠
&lt;/h3&gt;

&lt;p&gt;This is where the user interaction happens. A "Login" button will trigger the &lt;code&gt;chrome.identity&lt;/code&gt; flow.&lt;/p&gt;

&lt;p&gt;The core function is &lt;code&gt;chrome.identity.getAuthToken({ interactive: true }, ...)&lt;/code&gt;. The &lt;code&gt;interactive: true&lt;/code&gt; flag tells Chrome it's okay to show a user prompt if they aren't already logged in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getAuthToken() {
        return new Promise((resolve, reject) =&amp;gt; {
            chrome.identity.getAuthToken({ interactive: true }, function(token) {
                if (chrome.runtime.lastError) {
                    reject(chrome.runtime.lastError);
                } else {
                    resolve(token);
                }
            });
        });
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: The Service Worker (&lt;code&gt;background.js&lt;/code&gt;) ⚙️
&lt;/h3&gt;

&lt;p&gt;What about keeping the user logged in? Tokens expire! The service worker handles this gracefully in the background using the &lt;code&gt;chrome.alarms&lt;/code&gt; API. This is the Manifest V3-approved way to run periodic tasks like checking if an auth token needs a silent refresh.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function refreshToken() {
    try {
        const result = await chrome.storage.local.get(['accessToken']);
        if (result.accessToken) {
            // Try to get a new token
            const newToken = await new Promise((resolve, reject) =&amp;gt; {
                chrome.identity.getAuthToken({ interactive: false }, (token) =&amp;gt; {
                    if (chrome.runtime.lastError) {
                        reject(chrome.runtime.lastError);
                    } else {
                        resolve(token);
                    }
                });
            });

            if (newToken &amp;amp;&amp;amp; newToken !== result.accessToken) {
                await chrome.storage.local.set({ accessToken: newToken });
                console.log('Token refreshed successfully');
            }
        }
    } catch (error) {
        console.error('Token refresh failed:', error);
        // Clear invalid token
        await chrome.storage.local.clear();
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🛡️ Bonus Level: Fortifying Your Extension
&lt;/h3&gt;

&lt;p&gt;A working extension is great, but a &lt;strong&gt;secure&lt;/strong&gt; extension is professional. Here are the essential security measures we've baked in to make our extension enterprise-ready.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. The VIP List: Origin &amp;amp; Action Whitelisting 📜
&lt;/h4&gt;

&lt;p&gt;Your extension shouldn't talk to just any website. Use an &lt;code&gt;allowedOrigins&lt;/code&gt; array to ensure only your trusted web domains can send messages. In &lt;code&gt;manifest.json&lt;/code&gt;, enforce this with &lt;code&gt;externally_connectable&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. The Bouncer: Rate Limiting ✋
&lt;/h4&gt;

&lt;p&gt;To prevent DoS attacks, we allow a maximum of &lt;strong&gt;10 requests per minute&lt;/strong&gt; from any single origin. If a script goes over the limit, it gets rejected until the cooldown period is over.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. The Principle of Least Privilege: Data Minimization 📦
&lt;/h4&gt;

&lt;p&gt;When a trusted website asks for user information, we don't hand over the keys to the kingdom. We only provide the absolute minimum data necessary. &lt;strong&gt;Never send the access token to the web page.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. The Automatic Lock: Session Management ⏳
&lt;/h4&gt;

&lt;p&gt;Leaving a logged-in session active forever is a security risk. Our extension implements a &lt;strong&gt;4-hour session timeout&lt;/strong&gt;. If the user is inactive, their session data is cleared, and they will need to authenticate again.&lt;/p&gt;




&lt;h3&gt;
  
  
  You've Done It! 🎉
&lt;/h3&gt;

&lt;p&gt;Congratulations! You've successfully navigated the Manifest V3 maze and implemented a secure, modern, and user-friendly G Suite SSO login.&lt;/p&gt;

&lt;p&gt;By using the &lt;code&gt;chrome.identity&lt;/code&gt; API and layering in robust security measures, you've built an extension that is not only functional but also respects user privacy—the core principle of Manifest V3.&lt;/p&gt;




&lt;h3&gt;
  
  
  💻 Grab the Full Source Code
&lt;/h3&gt;

&lt;p&gt;Ready to see how all the pieces fit together? The complete, production-ready code for this extension is waiting for you on GitHub.&lt;/p&gt;

&lt;p&gt;Feel free to dive in, fork the repository, and use it as a launchpad for your own projects. Pull requests and stars are always welcome!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/JayMalli/G-Suite-SSO-Chrome-Extension" rel="noopener noreferrer"&gt;View the Project on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🤝 Let's Connect!
&lt;/h3&gt;

&lt;p&gt;If you found this guide helpful, let's keep the conversation going! I regularly post deep dives, security tips, and new projects I'm working on.&lt;/p&gt;

&lt;p&gt;Connect with me on LinkedIn—I'd love to hear about what you're building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;Find me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>chromeextension</category>
      <category>login</category>
      <category>scriptinjecterror</category>
      <category>manifestv3</category>
    </item>
    <item>
      <title>🔮 Keyboard Wizardry: How to Capture Any Key Press Across Windows with C#</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Fri, 02 May 2025 11:59:35 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/keyboard-wizardry-how-to-capture-any-key-press-across-windows-with-c-2c41</link>
      <guid>https://forem.com/jaymalli_programmer/keyboard-wizardry-how-to-capture-any-key-press-across-windows-with-c-2c41</guid>
      <description>&lt;p&gt;Have you ever wondered how applications like hotkey managers or keystroke recorders work their magic? How do they intercept keystrokes even when they're not in focus? The secret lies in something called "keyboard hooks" - a powerful yet somewhat mysterious feature of Windows programming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjzle3rhlwyv7x6d5vk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjzle3rhlwyv7x6d5vk3.png" alt="Magic" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this blog post, we'll demystify global keyboard hooks by building a simple WPF application that listens for the F1 key press anywhere in Windows. Whether you're a beginner looking to understand Windows hooks or an experienced developer wanting to implement global hotkeys, this guide will walk you through the process step by step.&lt;/p&gt;

&lt;p&gt;💪 Power in Your Hands: Once you master keyboard hooks, you can completely customize Windows' keyboard behavior - redirect keys to launch applications, block problematic keys, create custom shortcuts that work everywhere, or even transform standard keys into specialized functions for specific applications!&lt;/p&gt;

&lt;h2&gt;
  
  
  🪝 What Are Windows Hooks?
&lt;/h2&gt;

&lt;p&gt;Before diving into the code, let's understand what Windows hooks are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📘 Windows Hooks are mechanisms that let your application intercept events (like keyboard or mouse input) before they reach their intended application, allowing you to monitor or modify the system's behavior.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Think of Windows as a bustling city with messages constantly flowing 
between different parts of the system&lt;/li&gt;
&lt;li&gt;A hook is like setting up a checkpoint on a busy street&lt;/li&gt;
&lt;li&gt;This checkpoint lets you inspect (and potentially intercept) messages 
before they reach their destination&lt;/li&gt;
&lt;li&gt;You can choose to block certain messages while allowing others to pass 
through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: Imagine you're a traffic controller who only cares about red cars (F1 key presses). You set up a checkpoint (hook) where you can see all vehicles (keystrokes), but you only stop the red ones while letting everything else continue normally.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Overall Structure
&lt;/h3&gt;

&lt;p&gt;Our application is a simple WPF window that sets up a low-level keyboard hook to detect F1 key presses system-wide. Here's how the overall structure looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace MyWpfApp
{
    public partial class F1KeyListener : Window
    {
        // DLL imports and constants
        // Hook-related fields and delegates
        // Hook setup and callback methods
        // Cleanup code
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📦 Importing Native Methods
&lt;/h3&gt;

&lt;p&gt;Windows hooks require calling into native Windows functionality that isn't directly available in .NET. We use P/Invoke (Platform Invocation Services) to import these functions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📘 What is P/Invoke? P/Invoke is a technology that allows you to call functions in unmanaged DLLs (like Windows system DLLs) from your managed .NET code - think of it as a bridge between .NET and native Windows functionality.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What these imports do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SetWindowsHookEx: Establishes a hook in the Windows system&lt;/li&gt;
&lt;li&gt;UnhookWindowsHookEx: Removes a previously set hook&lt;/li&gt;
&lt;li&gt;CallNextHookEx: Passes the hook information to the next hook in the 
              chain&lt;/li&gt;
&lt;li&gt;GetModuleHandle: Retrieves a module handle for the specified module&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: Think of these imports as hiring specialized contractors who know how to work with the building's infrastructure (Windows OS). Since our .NET application doesn't natively "speak" the low-level Windows language, we need these translators to help us communicate with the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  📋 The Constants and Definitions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;

private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private LowLevelKeyboardProc _proc;
private IntPtr _hookId = IntPtr.Zero;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📘 What is a delegate? A delegate in C# is a type that represents references to methods with a specific parameter list and return type - essentially a type-safe function pointer that lets you pass methods as arguments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understanding the constants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WH_KEYBOARD_LL (value 13) tells Windows we want a low-level keyboard 
hook&lt;/li&gt;
&lt;li&gt;WM_KEYDOWN (0x0100) is the message sent when a key is pressed down&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;📘 What is IntPtr? &lt;br&gt;
IntPtr is a platform-specific type that represents a pointer or a handle - it's sized according to the platform's word size (32-bit or 64-bit) and is commonly used when interacting with native code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understanding the delegate and fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LowLevelKeyboardProc delegate defines the signature of our callback
function&lt;/li&gt;
&lt;li&gt;_proc is our instance of this callback&lt;/li&gt;
&lt;li&gt;_hookId stores the reference to our active hook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: These constants are like special codes or frequencies we need to tune into. The delegate defines the shape of our callback function - it's like specifying what information our checkpoint officer needs to receive to do their job.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔌 Setting Up the Hook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public F1KeyListener()
{
    _proc = HookCallback;
    StartHook();
}

private void StartHook()
{
    _hookId = SetHook(_proc);
}

private IntPtr SetHook(LowLevelKeyboardProc proc)
{
    using var curProcess = System.Diagnostics.Process.GetCurrentProcess();
    using var curModule = curProcess.MainModule;
    return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📘 What is Process.GetCurrentProcess? A method that retrieves an object representing the currently running process, giving you access to information about your application as it runs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Constructor steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create our callback method (_proc = HookCallback)&lt;/li&gt;
&lt;li&gt;Call StartHook() to set up the checkpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SetHook method breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gets information about our current process and module&lt;/li&gt;
&lt;li&gt;Calls SetWindowsHookEx with these parameters:

&lt;ul&gt;
&lt;li&gt;Hook type (WH_KEYBOARD_LL)&lt;/li&gt;
&lt;li&gt;Our callback method (proc)&lt;/li&gt;
&lt;li&gt;The module handle&lt;/li&gt;
&lt;li&gt;Thread ID (0 means all threads)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: It's like telling Windows: "Please notify this specific officer (HookCallback) whenever any keyboard activity happens in the system."&lt;/p&gt;

&lt;h3&gt;
  
  
  ❤️ The Heart of the Hook: The Callback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode &amp;gt;= 0 &amp;amp;&amp;amp; wParam == (IntPtr)WM_KEYDOWN)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Key key = KeyInterop.KeyFromVirtualKey(vkCode);

        if (key == Key.F1)
        {
            MessageBox.Show("F1 key was pressed!");
            return (IntPtr)1; // Prevent further processing
        }
    }

    return CallNextHookEx(_hookId, nCode, wParam, lParam);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;📘 What is Marshal.ReadInt32? Marshal.ReadInt32 is a method from the System.Runtime.InteropServices namespace that reads a 32-bit integer from unmanaged memory - it's used to extract data from pointers when working with native code.&lt;/p&gt;

&lt;p&gt;📘 What is KeyInterop? KeyInterop is a WPF utility class that converts between Windows virtual key codes and WPF Key values, making it easier to work with keyboard input in WPF applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Callback decision flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if the notification is valid (nCode &amp;gt;= 0) and if it's a key-down event&lt;/li&gt;
&lt;li&gt;Extract the virtual key code from the lParam&lt;/li&gt;
&lt;li&gt;Convert the Windows virtual key code to a WPF Key value&lt;/li&gt;
&lt;li&gt;If it's the F1 key:

&lt;ul&gt;
&lt;li&gt;Show a message box&lt;/li&gt;
&lt;li&gt;Return 1 (meaning "I'll handle this, don't pass it along")&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;For all other keys:

&lt;ul&gt;
&lt;li&gt;Call the next hook in the chain with CallNextHookEx&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Parameter meanings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nCode - Hook code; if negative, the hook procedure must pass the message&lt;/li&gt;
&lt;li&gt;wParam - Identifier of the keyboard message (WM_KEYDOWN, WM_KEYUP, etc.)&lt;/li&gt;
&lt;li&gt;lParam - Pointer to a structure containing details about the keystroke&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: Think of it as your checkpoint officer letting all vehicles pass except for the red ones (F1 keys), which get special treatment.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧹 Proper Cleanup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private void StopHook()
{
    if (_hookId != IntPtr.Zero)
    {
        UnhookWindowsHookEx(_hookId);
        _hookId = IntPtr.Zero;
    }
}

protected override void OnClosed(EventArgs e)
{
    StopHook();
    base.OnClosed(e);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleanup process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The StopHook() method:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks if we have an active hook&lt;/li&gt;
&lt;li&gt;Calls UnhookWindowsHookEx to remove our hook&lt;/li&gt;
&lt;li&gt;Resets the hook ID to zero&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The OnClosed override:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures StopHook() is called when the window closes&lt;/li&gt;
&lt;li&gt;Calls the base class implementation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Why cleanup matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents resource leaks&lt;/li&gt;
&lt;li&gt;Avoids interference with other applications&lt;/li&gt;
&lt;li&gt;Follows Windows programming best practices&lt;/li&gt;
&lt;li&gt;Ensures your application behaves properly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Real-Life Analogy: Good code is like a good camper - it always cleans up after itself! Without this, our checkpoint might keep operating even after we've packed up and gone home.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔤 Bonus: Capturing Key Combinations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private bool _isCtrlPressed = false;
private const int WM_KEYUP = 0x0101;

private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
    // Track When Key is Pressed
    if (nCode &amp;gt;= 0 &amp;amp;&amp;amp; wParam == (IntPtr)WM_KEYDOWN)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Key key = KeyInterop.KeyFromVirtualKey(vkCode);

        if (key == Key.LeftCtrl || key == Key.RightCtrl)
        {
            _isCtrlPressed = true;
        }
        else if (key == Key.C &amp;amp;&amp;amp; _isCtrlPressed)
        {
            MessageBox.Show("Ctrl+C was pressed!");
            _isCtrlPressed = false; // Reset the flag after handling
            return (IntPtr)1; // Prevent further processing
        }
    }

    // Track when Ctrl key is released
    else if (nCode &amp;gt;= 0 &amp;amp;&amp;amp; wParam == (IntPtr)WM_KEYUP)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Key key = KeyInterop.KeyFromVirtualKey(vkCode);

        if (key == Key.LeftCtrl || key == Key.RightCtrl)
        {
            _isCtrlPressed = false;
        }
    }

    return CallNextHookEx(_hookId, nCode, wParam, lParam);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🏁 Conclusion
&lt;/h3&gt;

&lt;p&gt;Our F1 key listener demonstrates the fundamental pattern that powers countless productivity tools, accessibility features, and gaming utilities. Next time you use a hotkey in your favorite application, you'll have a better understanding of the magic happening behind the scenes!&lt;/p&gt;

&lt;p&gt;📂 GitHub Repository&lt;br&gt;
Get the complete source code for this keyboard hook example and more at my GitHub repository: &lt;a href="https://github.com/JayMalli/Keyboard_Wizardry" rel="noopener noreferrer"&gt;github.com/JayMalli/Keyboard_Wizardry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔗 Connect with Me&lt;/p&gt;

&lt;p&gt;If you found this article helpful, I'd love to connect with you! Check out more of my work and stay updated.&lt;/p&gt;

&lt;p&gt;💼 Professional Network&lt;br&gt;
Connect with me on LinkedIn for more insights: &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;linkedin.com/in/jaymalli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🌐 Portfolio&lt;br&gt;
Visit my portfolio website to see more of my projects : &lt;a href="https://jaymalli.netlify.app/" rel="noopener noreferrer"&gt;Portfolio - Jay Malli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👍 Spread the Word&lt;br&gt;
Did you find this tutorial helpful? Please consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharing this article with fellow developers&lt;/li&gt;
&lt;li&gt;Giving the repository a star on GitHub&lt;/li&gt;
&lt;li&gt;Commenting below with your questions or keyboard hook creations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;📚 Further Resources&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/about-hooks" rel="noopener noreferrer"&gt;Microsoft Docs: Windows Hooks&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke" rel="noopener noreferrer"&gt;P/Invoke: Platform Invocation Services&lt;/a&gt;&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/input-overview" rel="noopener noreferrer"&gt;Low-level Keyboard Hooks in WPF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you built interesting applications using Windows hooks? Share your experiences in the comments below!&lt;/p&gt;

</description>
      <category>keybaordhooks</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>behindthescenestech</category>
    </item>
    <item>
      <title>Implement G-suit Chat Service in .NET Using Google Chat API</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Tue, 19 Dec 2023 18:32:07 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/implement-g-suit-chat-service-in-net-using-google-chat-api-7hp</link>
      <guid>https://forem.com/jaymalli_programmer/implement-g-suit-chat-service-in-net-using-google-chat-api-7hp</guid>
      <description>&lt;p&gt;Hello Devs! Today, we'll delve into the implementation of G Suite Chat Service in .NET using C#. Our primary focus will be on the service implementation rather than the application itself. Feel free to choose any application type based on your specific requirements. For the purpose of this guide, I'll be using a .NET Maui app to demonstrate the service implementation. Let's get started on incorporating G Suite Chat functionality into your .NET projects!&lt;/p&gt;

&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Initial Setup&lt;/li&gt;
&lt;li&gt;Configure Our App &amp;amp; Services&lt;/li&gt;
&lt;li&gt;Chat Service Code&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step - 1 : Initial Setup &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1.1 : Get Client Id &amp;amp; Secret as well as access &amp;amp; refresh token.
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Before we start coding, create an API key and credentials for your application in the GCP console. And after that get access_token &amp;amp; refresh_token by authorizing user using Oauth service. Check out this blog for step-by-step instructions: &lt;a href="https://dev.to/jaymalli_programmer/google-oauth-20-authorization-service-implementation-in-net-maui-okl"&gt;blog reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;=&amp;gt; Follow steps 1 to 3 outlined in the above-mentioned blog to create a client ID and client secret.&lt;br&gt;
=&amp;gt; Follow steps 4 to 7 outlined in the above-mentioned blog to get the access_token &amp;amp; refresh_token.&lt;/p&gt;
&lt;h4&gt;
  
  
  1.2 : Add scopes in Admin Console
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Add the following scopes in the Admin Console under Security &amp;gt;&amp;gt; Access And Data Control &amp;gt;&amp;gt; API controls &amp;gt;&amp;gt; Domain-Wide delegation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://admin.google.com" rel="noopener noreferrer"&gt;Admin Console&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.googleapis.com/auth/contacts" rel="noopener noreferrer"&gt;https://www.googleapis.com/auth/contacts&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.googleapis.com/auth/contacts.other.readonly" rel="noopener noreferrer"&gt;https://www.googleapis.com/auth/contacts.other.readonly&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.googleapis.com/auth/chat.messages" rel="noopener noreferrer"&gt;https://www.googleapis.com/auth/chat.messages&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.googleapis.com/auth/chat.spaces" rel="noopener noreferrer"&gt;https://www.googleapis.com/auth/chat.spaces&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : If you encounter a '400 Invalid Scope' error, resolve it by removing the scope '&lt;a href="https://www.googleapis.com/auth/chat.bot" rel="noopener noreferrer"&gt;https://www.googleapis.com/auth/chat.bot&lt;/a&gt;' from both the Admin Console settings and any corresponding sections within your code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;=&amp;gt; For detailed steps on adding scopes in the Admin Console, check out this &lt;a href="https://dev.to/jaymalli_programmer/gsuitgoogle-api-integration-in-net-maui-3kch"&gt;blog&lt;/a&gt;. &lt;/p&gt;


&lt;h3&gt;
  
  
  Step - 2 : Configure Our App &amp;amp; Services &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;h4&gt;
  
  
  2.1 : Add necessary NuGet packages
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Install the NuGet package from the this &lt;a href="https://www.nuget.org" rel="noopener noreferrer"&gt;website&lt;/a&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google.Apis.HangoutsChat.v1 (Recommended version: 1.64.0.3230)&lt;/li&gt;
&lt;li&gt;Google.Apis.PeopleService.v1 (Recommended version: 1.64.0.3093)&lt;/li&gt;
&lt;li&gt;Google.Apis (Recommended version: 1.64.0)&lt;/li&gt;
&lt;li&gt;Google.Apis.Core (Recommended version: 1.64.0)&lt;/li&gt;
&lt;li&gt;Google.Apis.Auth (Recommended version : 1.64.0)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : Keep in mind that if you'll use pacakge 1.60 or below that then you'll getting this error in future : "userChatService.Spaces.List()" method is not found in given namespace , so you have to use latest version &amp;amp; if there are other google packages with version 1.60 or below which was creating version conflicting issue then upgrade all google pacakges with its latest version.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  2.2 : Enable the required APIs
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Enable the following Google APIs from the Google Cloud Console under APIs &amp;amp; Services &amp;gt;&amp;gt; Library section.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People API&lt;/li&gt;
&lt;li&gt;Google Chat API&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To address the error, 'The service chat has thrown an exception. HttpStatusCode is NotFound. Google Chat app not found. To create a Chat app, you must turn on the Chat API and configure the app in the Google Cloud console,' configure the app in the Chat API section of the Google Cloud Console. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;=&amp;gt; Navigate to the CONFIGURATION tab in the Google Chat API.&lt;/p&gt;

&lt;p&gt;=&amp;gt; Complete the necessary details, including App name, Avatar URL, Description, and App URL in the CONFIGURATION tab of the Google Chat API. Save the changes by clicking the save button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnainete0lum27owdsjuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnainete0lum27owdsjuq.png" alt="google chat API configuration" width="687" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq3n0940hm6jvxzkgj52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq3n0940hm6jvxzkgj52.png" alt="save the changes of chat api configuration" width="623" height="474"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Step - 3 : Let's do some code &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;h4&gt;
  
  
  3.1 : Get User Credentials
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Generate user credentials from the access_token to gain access to different G Suite Chat and People services for that user. Following this, initialize the user's chat service and people service. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ChatService.cs :&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.HangoutsChat.v1;
using Google.Apis.HangoutsChat.v1.Data;
using Google.Apis.PeopleService.v1;
using Google.Apis.PeopleService.v1.Data;
using Google.Apis.Services;

namespace YOUR_APP.services
{
    public class ChatService
    {
        public HangoutsChatService _hangoutsChatService { get; set; }
        public PeopleServiceService _peopleService { get; set; }
        public ChatService(string access_token, string refresh_token)
        {
            UserCredential userCredential = GenerateCredentialFromAccessToken(access_token, refresh_token);

            _peopleService = new PeopleServiceService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = userCredential,
                ApplicationName = "YOUR_APP_NAME",
            });
            _hangoutsChatService = new HangoutsChatService(new BaseClientService.Initializer() { HttpClientInitializer = userCredential, ApplicationName = "YOUR_APP_NAME" });

        }
        private UserCredential GenerateCredentialFromAccessToken(string accessToken, string refreshToken)
        {
            UserCredential credential;
            string[] Scopes = { PeopleServiceService.Scope.Contacts, PeopleServiceService.Scope.ContactsOtherReadonly, HangoutsChatService.Scope.ChatSpaces, HangoutsChatService.Scope.ChatMessages };

            TokenResponse tokenResponse = new TokenResponse
            {
                AccessToken = accessToken,
                RefreshToken = refreshToken
            };

            var initializer = new GoogleAuthorizationCodeFlow.Initializer
            {
                // get from gcp console under  API &amp;amp; Services &amp;gt;&amp;gt; Credentials section
                ClientSecrets = new ClientSecrets
                {
                    ClientId = "your_app_client_id",
                    ClientSecret = "your_app_client_secret"
                },
                Scopes = Scopes,
            };

            credential = new UserCredential(new GoogleAuthorizationCodeFlow(initializer), userId: "user", tokenResponse);

            return credential;
        }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.2 : Retrive Spaces
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Retrieve all spaces for a user, encompassing chats between two individuals, group conversations, or discussions involving multiple members within a space.&lt;/p&gt;

&lt;p&gt;=&amp;gt; Identify individual user spaces with the type 'DIRECT_MESSAGE' and spaces containing multiple members with the type 'SPACE'.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ChatService.cs :&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...

// other code

...

 private List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt; GetAllSpaces()
        {
            List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt; spaces = new List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt;();
            try
            {
                SpacesResource.ListRequest req = _hangoutsChatService.Spaces.List();
                req.Fields = "spaces(name,displayName,spaceType)";
                ListSpacesResponse res = req.Execute();
                if (res.Spaces.Count &amp;gt; 0)
                {
                    foreach (Space space in res.Spaces)
                    {
                        Dictionary&amp;lt;string, string&amp;gt; dict = new Dictionary&amp;lt;string, string&amp;gt;();
                        dict.Add("id", space.Name);
                        dict.Add("displayName", space.DisplayName);
                        dict.Add("type", space.SpaceType);
                        spaces.Add(dict);
                    }
                }

            }
            catch (GoogleApiException ex)
            {
                Console.WriteLine(ex.Message);
            }
            return spaces;
        }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftipl8kpa6v4pfi34pnph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftipl8kpa6v4pfi34pnph.png" alt="Gsuit chat spaces" width="412" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3.2 : Retrive Chats
&lt;/h4&gt;

&lt;p&gt;=&amp;gt; Retrieve chats from each space, including sender information and thread details.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ChatService.cs :&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
// other code
...

        public List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt; GetChatsOfSpace(string spaceId)
        {
            List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt; chats = new List&amp;lt;Dictionary&amp;lt;string, string&amp;gt;&amp;gt;();
            try
            {
                SpacesResource.MessagesResource.ListRequest req = _hangoutsChatService.Spaces.Messages.List(spaceId);
                req.Fields = "messages(sender,text,name,thread)";
                ListMessagesResponse res = req.Execute();
                if (res.Messages != null)
                {
                    foreach (Message msg in res.Messages)
                    {
                        Dictionary&amp;lt;string, string&amp;gt; dict = new Dictionary&amp;lt;string, string&amp;gt;();
                        dict.Add("threadId", msg.Thread.Name);
                        dict.Add("text", msg.Text);
                        string senderName = GetSenderOfChat(msg.Sender.Name);
                        dict.Add("senderName", senderName);
                        chats.Add(dict);
                    }
                }

                return chats;

            }
            catch (GoogleApiException ex)
            {
                Console.WriteLine(ex.Message);
                return chats;
            }

        }

        private string GetSenderOfChat(string senderId)
        {
            string resourceName = senderId.Replace("users/", "people/");
            PeopleResource.GetRequest req = _peopleService.People.Get(resourceName);
            req.PersonFields = "names";
            Person person = req.Execute();
            if (person?.Names != null &amp;amp;&amp;amp; person.Names.Count &amp;gt; 0)
            {
                return person.Names[0].DisplayName;
            }

            return "Unknown User";

        }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fus9kio3g6u9k49pksf2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fus9kio3g6u9k49pksf2g.png" alt="chats of space" width="556" height="417"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;=&amp;gt; If you want to explore more about the Google Chat API, including available methods, required scopes, and the request-response body of different methods, refer to this resource : &lt;a href="https://developers.google.com/chat/api/reference/rest" rel="noopener noreferrer"&gt;Google Chat API&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;=&amp;gt; You can also refer to this documentation for the Google People API. &lt;a href="https://developers.google.com/people/api/rest" rel="noopener noreferrer"&gt;Google People API&lt;/a&gt;    &lt;/p&gt;

&lt;p&gt;=&amp;gt; The example above demonstrates how to implement google chat/hangout service in .NET MAUI using c#, you can find the necessary code in the repository mentioned below. &lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;a href="https://github.com/JayMalli/Chat_Service_Maui" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on LinkedIn. You can find me at &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>gsuit</category>
      <category>chat</category>
    </item>
    <item>
      <title>Develop &amp; Publish .NET MAUI Application for Mac using Visual Studio Code</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Fri, 01 Dec 2023 12:39:08 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/develop-publish-net-maui-application-for-mac-using-visual-studio-code-4jcj</link>
      <guid>https://forem.com/jaymalli_programmer/develop-publish-net-maui-application-for-mac-using-visual-studio-code-4jcj</guid>
      <description>&lt;p&gt;Hello Developers,&lt;/p&gt;

&lt;p&gt;=&amp;gt; So today we are developing a basic app using .NET maui framework &amp;amp; we'll publish it for the Mac platform.&lt;/p&gt;

&lt;p&gt;=&amp;gt; .NET MAUI, which stands for .NET Multi-platform App UI, is an open-source, cross-platform framework for building native mobile and desktop applications. It is an evolution of the Xamarin.Forms framework and is part of the broader .NET ecosystem.&lt;/p&gt;

&lt;p&gt;Before we start our work , let's install required tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 1 : Installation task&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;dotnet SDK (Recommend : SDK 7.0)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/7.0" rel="noopener noreferrer"&gt;dotnet Runtime (Recommend : 7.0)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://xcodereleases.com/" rel="noopener noreferrer"&gt;XCode&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; Add required Extensions in vs code&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C# (by Microsoft)&lt;/li&gt;
&lt;li&gt;.NET Runtime Install Tool (by Microsoft)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; after that open settings &amp;amp; search Omnisharp , make sure that &lt;br&gt;
   "Use Omnisharp" checkbox is enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjx6zffof158cu5lr9eu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjx6zffof158cu5lr9eu.png" alt="omnisharp" width="685" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; check above tools are installed successfully or not by following commands from terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet --list-sdks&lt;br&gt;
dotnet  --list-runtimes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68mlqaqa05z9rf05i3ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F68mlqaqa05z9rf05i3ce.png" alt="workload" width="676" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Check whether maui workload is exist not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload list&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lj6pxbew5ojymjqheyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2lj6pxbew5ojymjqheyi.png" alt="workload" width="677" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; List all available workloads. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload search&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frip0f45zgh7f4segwl1a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frip0f45zgh7f4segwl1a.png" alt="workload" width="668" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Install maui workload&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload install maui&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; Check installed workload&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload list &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61hnnwvcg9q1i1h9i7pg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61hnnwvcg9q1i1h9i7pg.png" alt="workload" width="677" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 2 : Create Maui Project&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;-&amp;gt; Create Maui Template.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet new maui  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4n6l0uwvkf9tonglrkue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4n6l0uwvkf9tonglrkue.png" alt="maui template" width="679" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; open the [project_name].csproj file &amp;amp; perform following acions&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;remove net7.0-android &lt;br&gt;
comment windows platform tag&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fav3v53slxfhu77h60c0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fav3v53slxfhu77h60c0v.png" alt="csproj file" width="772" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; perform restore &amp;amp; build. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet restore (add nuget packages,mentioned in .csproj file)&lt;/p&gt;

&lt;p&gt;dotnet build&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnu7qvhiwa42iln6a16v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnu7qvhiwa42iln6a16v.png" alt="dotnet build" width="367" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; You may encounter the following error. You can either upgrade the Xcode version or add the following tags in the .csproj file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gi1k1q09gai17wlgmj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4gi1k1q09gai17wlgmj6.png" alt="Error" width="800" height="32"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PropertyGroup&amp;gt;
     &amp;lt;ErrorOnDuplicatePublishOutputFiles&amp;gt;false&amp;lt;/ErrorOnDuplicatePublishOutputFiles&amp;gt;
        &amp;lt;MtouchLink&amp;gt;SdkOnly&amp;lt;/MtouchLink&amp;gt;
        &amp;lt;GenerateRuntimeConfiguratonFiles&amp;gt;true&amp;lt;/GenerateRuntimeConfiguratonFiles&amp;gt;
    &amp;lt;/PropertyGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;dotnet build&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1a2jicndd6h8didqfr9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1a2jicndd6h8didqfr9.png" alt="build" width="800" height="45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 3 : Let's do some code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; Add fonts :&lt;/p&gt;

&lt;p&gt;-&amp;gt; Add OpenSans-Regular.ttf &amp;amp; OpenSans-Semibold.ttf  fonts file in the Resources/Fonts folder. (check out my GitHub repo for required resources : &lt;a href="https://github.com/JayMalli/groot_app_maui_mac_publish" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;)  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuv85nt7nh42s745itws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuv85nt7nh42s745itws.png" alt="fonts" width="437" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; AppShell.xaml :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove title from header
&amp;lt;ShellContent
    ContentTemplate="{DataTemplate local:MainPage}"
    Route="MainPage"
    Shell.NavBarIsVisible="False" 
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; MainPage.xaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Groot_App_Maui.MainPage"&amp;gt;

    &amp;lt;ScrollView&amp;gt;
        &amp;lt;VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center"&amp;gt;

            &amp;lt;Image
                Source="groot_img.jpg" // Add this image in resources folder
                SemanticProperties.Description="Cute groot waving hi to you!"
                HeightRequest="400"
                HorizontalOptions="Center" /&amp;gt;

            &amp;lt;Label
                Text="I am Groot!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" /&amp;gt;

              &amp;lt;Button
                FontAttributes="Bold"  
                Text="Click Me"  
                FontSize="{OnPlatform WinUI=16, MacCatalyst=18}"
                HorizontalOptions="Center"
                Clicked="OnClickBtn"  
             /&amp;gt; 

        &amp;lt;/VerticalStackLayout&amp;gt;
    &amp;lt;/ScrollView&amp;gt;

&amp;lt;/ContentPage&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; Add below image in Resources/Images folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeq95ei1waiqx2pm7xj5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeq95ei1waiqx2pm7xj5.png" alt="resources" width="400" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; MainPage.xaml.cs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove unnecessary code

namespace Groot_App_Maui;

public partial class MainPage : ContentPage
{

    public MainPage()
    {
        InitializeComponent();
    }

        public async void OnClickBtn(object sender, EventArgs e)
    {
        await 
 Application.Current.MainPage.DisplayAlert(null, "Again I am Groot :)", "OK");
    } 
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Add icon image in Resources/AppIcon folder&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxalfkte5afi4sx1bezg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxalfkte5afi4sx1bezg9.png" alt="icon" width="421" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; [project_name].csproj :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// other code
&amp;lt;ItemGroup&amp;gt;
        &amp;lt;!-- App Icon --&amp;gt;
        &amp;lt;MauiIcon Include="Resources\AppIcon\app_icon.png"/&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37phmm5vxtkz5ra1a24o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37phmm5vxtkz5ra1a24o.png" alt="icon" width="770" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; modify icon name in /Platforms/MacCataslyst/Info.plist file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    &amp;lt;key&amp;gt;XSAppIconAssets&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;Assets.xcassets/groot_app_icon.appiconset&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; modify icon name in /Platforms/iOS/Info.plist file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;key&amp;gt;XSAppIconAssets&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;Assets.xcassets/groot_app_icon.appiconset&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Modify Properties/launchSettings.json file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "profiles": {
    "Windows (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Windows (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "Mac (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Mac (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step - 4 : lets run our app&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet build&lt;br&gt;
dotnet run -f net7.0-maccatalyst (from .csproj file)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; yaahh!!!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcaqc71ca6m1r6m628xr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdcaqc71ca6m1r6m628xr.png" alt="o/p image" width="790" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 5 : Its time to publish our app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; We'll create a pkg for our app.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Modify bundleId in .csproj file&lt;br&gt;
&lt;code&gt;&amp;lt;ApplicationId&amp;gt;com.companyname.groot_app_maui&amp;lt;/ApplicationId&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Modify Info.plist file for mac &amp;amp; ios&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;key&amp;gt;CFBundleIdentifier&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;com.companyname.groot_app_maui&amp;lt;/string&amp;gt;
&amp;lt;key&amp;gt;CFBundleDevelopmentRegion&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;en&amp;lt;/string&amp;gt;
    &amp;lt;key&amp;gt;NSHumanReadableCopyright&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;Groot © 2023&amp;lt;/string&amp;gt;
    &amp;lt;key&amp;gt;ITSAppUsesNonExemptEncryption&amp;lt;/key&amp;gt;
    &amp;lt;false/&amp;gt;
    &amp;lt;key&amp;gt;NSDownloadsUsageDescription&amp;lt;/key&amp;gt;
    &amp;lt;string&amp;gt;We need access to your Downloads folder to do XYZ.&amp;lt;/string&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Create Entitlements.plist file Platforms/mac &amp;amp; add below code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;
&amp;lt;plist version="1.0"&amp;gt;
  &amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.app-sandbox&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.network.client&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.cs.allow-jit&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;&amp;lt;!-- Specify permissions for accessing user's location --&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.personal-information.location&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;&amp;lt;!-- Specify permissions for accessing user's contacts --&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.personal-information.contacts&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;&amp;lt;!-- Specify permissions for accessing user's photos --&amp;gt;
    &amp;lt;key&amp;gt;com.apple.security.assets.photos.read-write&amp;lt;/key&amp;gt;
    &amp;lt;true/&amp;gt;
  &amp;lt;/dict&amp;gt;
&amp;lt;/plist&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; Publish app without signed certificate&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet publish -f net7.0-maccatalyst -c Release -r maccatalyst-x64&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; If you encounter this error in the production app,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Attempting to JIT compile method '(wrapper delegate-invoke) Google.Apis.Drive.v3.Data.User :invoke_callvirt_User_About (Google.Apis.Drive.v3.Data.About)' while running in aot-only mode. See &lt;a href="https://docs.microsoft.com/xamarin/ios/internals/limitations" rel="noopener noreferrer"&gt;https://docs.microsoft.com/xamarin/ios/internals/limitations&lt;/a&gt; for more information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then add UseInterpreter tag in the csproj file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;PropertyGroup&amp;gt;
     &amp;lt;ErrorOnDuplicatePublishOutputFiles&amp;gt;false&amp;lt;/ErrorOnDuplicatePublishOutputFiles&amp;gt;
        &amp;lt;MtouchLink&amp;gt;SdkOnly&amp;lt;/MtouchLink&amp;gt;
        &amp;lt;UseInterpreter&amp;gt;true&amp;lt;/UseInterpreter&amp;gt;&amp;lt;GenerateRuntimeConfiguratonFiles&amp;gt;true&amp;lt;/GenerateRuntimeConfiguratonFiles&amp;gt;
    &amp;lt;/PropertyGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1rkgfendm2t770bzqmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1rkgfendm2t770bzqmh.png" alt="pkg path" width="800" height="23"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Open the terminal and execute the following command to monitor the logs of the app while the installation task is currently executing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;tail -f /private/var/log/install.log&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; Additionally, open the Console and click on the start button to monitor logs of our app and view crash reports if the app crashes.&lt;/p&gt;

&lt;p&gt;-&amp;gt; If you encounter this error in the console after installing the app from the app package and your published app is not working, then ignore it. There is no reason to believe that the app is not working because of this error. I also got this error in my console, &amp;amp; still app is working. :) &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Non-fatal error enumerating at , continuing: Error Domain=NSCocoaErrorDomain Code=260 "The file “PlugIns” couldn’t be opened because there is no such file." UserInfo={NSURL=PlugIns/ -- app_path/Contents/, NSFilePath=/Users/mufaddalbharmal/Desktop/app_name.app/Contents/PlugIns, NSUnderlyingError=0x7f7d61708ad0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9szxv6ikjdgomblduxq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9szxv6ikjdgomblduxq.png" alt="app installer" width="625" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; You can see in the yellow-highlighted part that there is no sign of a lock, which means our app is not verified.&lt;/p&gt;

&lt;p&gt;=&amp;gt; Create Signed Certificate :&lt;/p&gt;

&lt;p&gt;-&amp;gt; First, we need to create a signed certificate. Follow the steps outlined in the provided documentation on creating signed certificates for your app: &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/mac-catalyst/deployment/publish-outside-app-store?view=net-maui-8.0" rel="noopener noreferrer"&gt;Docs for certificate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Now, publish our app with the signature created by the certificate. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet publish -f net7.0-maccatalyst -c Release -p:MtouchLink=SdkOnly -p:CreatePackage=true -p:EnableCodeSigning=true -p:EnablePackageSigning=true -p:CodesignKey="your_certificate_codesignkey" -p:CodesignProvision="DSR_DevID" -p:CodesignEntitlements="Platforms\MacCatalyst\Entitlements.plist" -p:PackageSigningKey="your_certificate_codesignkey" -p:UseHardenedRuntime=true&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;where ,&lt;/p&gt;

&lt;p&gt;CodesignKey : The name of the code signing key. Set to the name of your distribution certificate, as displayed in Keychain Access. &lt;br&gt;
e.g.CodesignKey="Developer ID Application: *** (***)"&lt;/p&gt;

&lt;p&gt;PackageSigningKey : same as CodesignKey&lt;/p&gt;

&lt;p&gt;CodesignProvision :The provisioning profile to use when signing the app bundle.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Now you can see there is a lock at the top right corner, indicating that our app is verified and trusted. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf8ldi7umi9151ankzto.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf8ldi7umi9151ankzto.png" alt="app installer with lock" width="614" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Yaah!!! our production app ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F600y6woki2lab2ibzkns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F600y6woki2lab2ibzkns.png" alt="app icon in menu bar" width="382" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy11u43j47po57wyxzri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy11u43j47po57wyxzri.png" alt="app demo" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; The example above demonstrates how to develop &amp;amp; publish .NET maui app for Mac platform , you can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;a href="https://github.com/JayMalli/groot_app_maui_mac_publish" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. You can find me at LinkedIn&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>mac</category>
      <category>publish</category>
    </item>
    <item>
      <title>Develop &amp; Publish .NET MAUI Application for Windows using Visual Studio Code</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Sat, 25 Nov 2023 16:42:44 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/develop-publish-net-maui-application-for-windows-using-visual-studio-code-13d0</link>
      <guid>https://forem.com/jaymalli_programmer/develop-publish-net-maui-application-for-windows-using-visual-studio-code-13d0</guid>
      <description>&lt;p&gt;Hello Developers,&lt;/p&gt;

&lt;p&gt;=&amp;gt; So today we are developing a basic app using .NET maui framework &amp;amp; we'll publish it for the Windows platform.&lt;/p&gt;

&lt;p&gt;=&amp;gt; .NET MAUI, which stands for .NET Multi-platform App UI, is an open-source, cross-platform framework for building native mobile and desktop applications. It is an evolution of the Xamarin.Forms framework and is part of the broader .NET ecosystem.&lt;/p&gt;

&lt;p&gt;Before we start our work , let's install required tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 1 : Installation task&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;dotnet SDK (Recommend : SDK 7.0)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dotnet.microsoft.com/en-us/download/dotnet/7.0" rel="noopener noreferrer"&gt;dotnet Runtime (Recommend : 7.0)&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; Add required Extensions in vs code&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C# (by Microsoft)&lt;/li&gt;
&lt;li&gt;.NET Runtime Install Tool (by Microsoft)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;-&amp;gt; after that open settings &amp;amp; search Omnisharp , make sure that &lt;br&gt;
   "Use Omnisharp" checkbox is enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueajmnpc4vwocv488x3m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fueajmnpc4vwocv488x3m.png" alt="C# extension omnisharp" width="800" height="290"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff89wdnp2n7093re6l1e2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff89wdnp2n7093re6l1e2.png" alt="omnisharp auto start" width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; check above tools are installed successfully or not by following commands from terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet --list-sdks&lt;br&gt;
dotnet  --list-runtimes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vdx6qvxx9mm0t2hq1sx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vdx6qvxx9mm0t2hq1sx.png" alt="dotnet sdk" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Check whether maui workload is exist not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload list&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6d0f5v3by8z1h6qxmbtg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6d0f5v3by8z1h6qxmbtg.png" alt="maui workload" width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; List all available workloads. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload search&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmq59i7d71kn9xcrl9kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmq59i7d71kn9xcrl9kx.png" alt="workload list" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Install maui workload&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload install maui&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; Check installed workload&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet workload list &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoomlrq8o6t22j7ew23j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcoomlrq8o6t22j7ew23j.png" alt="maui workload" width="800" height="214"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 2 : Create Maui Project&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;-&amp;gt; Create Maui Template.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet new maui  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kuypn37d8ojq3bx6d2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3kuypn37d8ojq3bx6d2l.png" alt="Maui Template" width="800" height="53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; open the [project_name].csproj file &amp;amp; perform following acions&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;comment this tag net7.0-android;net7.0-ios;net7.0-maccatalyst &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4pnvat501oi1ufnu1gy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4pnvat501oi1ufnu1gy.png" alt="target framework" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet restore (add nuget packages,mentioned in .csproj file)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdu67r1ir8eeqb2wr7jef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdu67r1ir8eeqb2wr7jef.png" alt="restore" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet build&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4poqehd67m0grj2s7d5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4poqehd67m0grj2s7d5x.png" alt="build" width="321" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 3 : Let's do some code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; Add fonts :&lt;/p&gt;

&lt;p&gt;-&amp;gt; Add OpenSans-Regular.ttf &amp;amp; OpenSans-Semibold.ttf  fonts file in the Resources/Fonts folder. (check out my github repo for required resources : &lt;a href="https://github.com/JayMalli/groot_app-maui-windows_publish/tree/main" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;)  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1lckdyherhzvbjk593r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1lckdyherhzvbjk593r.png" alt="font" width="356" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; AppShell.xaml :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove title from header
&amp;lt;ShellContent
    ContentTemplate="{DataTemplate local:MainPage}"
    Route="MainPage"
    Shell.NavBarIsVisible="False" 
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; MainPage.xaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="utf-8" ?&amp;gt;
&amp;lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Groot_App_Maui.MainPage"&amp;gt;

    &amp;lt;ScrollView&amp;gt;
        &amp;lt;VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center"&amp;gt;

            &amp;lt;Image
                Source="groot_img.jpg" // Add this image in resources folder
                SemanticProperties.Description="Cute groot waving hi to you!"
                HeightRequest="400"
                HorizontalOptions="Center" /&amp;gt;

            &amp;lt;Label
                Text="I am Groot!"
                SemanticProperties.HeadingLevel="Level1"
                FontSize="32"
                HorizontalOptions="Center" /&amp;gt;

              &amp;lt;Button
                FontAttributes="Bold"  
                Text="Click Me"  
                FontSize="{OnPlatform WinUI=16, MacCatalyst=18}"
                HorizontalOptions="Center"
                Clicked="OnClickBtn"  
             /&amp;gt; 

        &amp;lt;/VerticalStackLayout&amp;gt;
    &amp;lt;/ScrollView&amp;gt;

&amp;lt;/ContentPage&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Please add the Groot image to the Resources/Images folder and the Groot app icon to the Resources/AppIcon folder. You can obtain these files from this &lt;a href="https://github.com/JayMalli/groot_app-maui-windows_publish/tree/main" rel="noopener noreferrer"&gt;repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7fjpsgs2ipcutvp9vw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7fjpsgs2ipcutvp9vw0.png" alt="resources" width="357" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspkx2k4p2a994cfae8o3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fspkx2k4p2a994cfae8o3.png" alt="app icon" width="356" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; MainPage.xaml.cs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove unnecessary code

namespace Groot_App_Maui;

public partial class MainPage : ContentPage
{

    public MainPage()
    {
        InitializeComponent();
    }

        public async void OnClickBtn(object sender, EventArgs e)
    {
        await 
 Application.Current.MainPage.DisplayAlert(null, "Again I am Groot :)", "OK");
    } 
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; [project_name].csproj :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// other code
&amp;lt;ItemGroup&amp;gt;
        &amp;lt;!-- App Icon --&amp;gt;
        &amp;lt;MauiIcon Include="Resources\AppIcon\app_icon.png"/&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8uazkqpz0zrz3dvqtu35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8uazkqpz0zrz3dvqtu35.png" alt="app icon" width="785" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 4 : Lets run our app&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet run -f net7.0-windows10.0.19041.0 (from .csproj file)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9j7rdknx77nnt8ru78in.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9j7rdknx77nnt8ru78in.png" alt="Profile error" width="800" height="81"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawe0x776u2ewff90jwwi.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawe0x776u2ewff90jwwi.jpg" alt="Groot happy" width="600" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; yaa yaa! "I know Groot; I forgot to modify the launch settings file of the app."&lt;/p&gt;

&lt;p&gt;-&amp;gt; The launchSettings.json is used to configure various settings related to the debugging and launching of your application for different environments and configurations.&lt;/p&gt;

&lt;p&gt;=&amp;gt; Modify Properties/launchSettings.json file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "profiles": {
    "Windows (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Windows (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device windows --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "Mac (Debug)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Mac (Release)": {
      "commandName": "Project",
      "commandLineArgs": "--device macos --theme Light",
      "workingDirectory": "$(ProjectDir)",
      "launchBrowser": true,
      "launchUrl": "",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;-&amp;gt; Again run app&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet build &lt;br&gt;
dotnet run -f net7.0-windows10.0.19041.0 &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; If the app is not launched or running, then check the Events Log in the Event Viewer,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Search Menu &amp;gt;&amp;gt; Event Viewer &amp;gt;&amp;gt; Windows Logs &amp;gt;&amp;gt; Application&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; If you encounter this error, add the following code to the .csproj file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmodv0tium0v4b0g87hn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpmodv0tium0v4b0g87hn.png" alt="Event logs" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; .csproj file :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;WindowsAppSDKSelfContained&amp;gt;true&amp;lt;/WindowsAppSDKSelfContained&amp;gt;&amp;lt;WindowsPackageType&amp;gt;None&amp;lt;/WindowsPackageType&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7c97bnvoxsb9dlggw94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe7c97bnvoxsb9dlggw94.png" alt="csproj file" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Again run app&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet build &lt;br&gt;
dotnet run -f net7.0-windows10.0.19041.0 &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Yaahh!
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn64hxu6knz9fr55jo906.jpg" alt="app o/p" width="800" height="442"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step - 5 : Its time to publish our app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; We will create an MSIX package to publish the app. For that, we need to create a self-signed certificate to demonstrate our app.&lt;/p&gt;

&lt;p&gt;-&amp;gt; If you want to learn more about the MSIX package, please visit: &lt;a href="https://learn.microsoft.com/en-us/windows/msix/overview" rel="noopener noreferrer"&gt;MSIX Docs&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;=&amp;gt; Create Self Signed Certificate : &lt;/p&gt;

&lt;p&gt;-&amp;gt; Open PowerShell as an administrator and execute the following commands:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;New-SelfSignedCertificate -Type Custom &lt;code&gt;&lt;br&gt;
                          -Subject "CN=&amp;lt;PublisherName&amp;gt;"&lt;/code&gt;&lt;br&gt;
                          -KeyUsage DigitalSignature &lt;code&gt;&lt;br&gt;
                          -FriendlyName "My temp dev cert"&lt;/code&gt;&lt;br&gt;
                          -CertStoreLocation "Cert:\CurrentUser\My" `&lt;br&gt;
                          -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;// replace PublisherName with name of author e.g. CN=JayMalli&lt;/p&gt;

&lt;p&gt;-&amp;gt; You will receive the thumbprint of the certificate upon successful creation. Copy that thumbprint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6hny79nr3hxx0lexpwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6hny79nr3hxx0lexpwe.png" alt="certificate thumbprint" width="767" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Execute this command in PowerShell to get all thumbprints.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get-ChildItem "Cert:\CurrentUser\My" | Format-Table Subject, FriendlyName, Thumbprint &lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;=&amp;gt; Export Certificate :&lt;/p&gt;

&lt;p&gt;-&amp;gt; Open the 'Manage User Certificates' dialogue box using the search menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3c1ujgy4m6jfz6iqiup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3c1ujgy4m6jfz6iqiup.png" alt="Manage User Certificates" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Go to Personal&amp;gt;&amp;gt;Certificates&amp;gt;&amp;gt; tab&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpzde5cdiqrues400g2f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpzde5cdiqrues400g2f.png" alt="not trusted certificate" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; The self signed certificate is not verified &amp;amp; valid for 1 year.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Export the certificate by following these steps.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Navigate to the details tab in that certificate dialogue box ,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Click on Copy to file button &amp;gt;&amp;gt; Next&lt;br&gt;
do not export private key &amp;gt;&amp;gt; Next&lt;br&gt;
DERbinary Encoded &amp;gt;&amp;gt; Next&lt;br&gt;
Specify location &amp;amp; file name by browse option &amp;gt;&amp;gt; Next&lt;br&gt;
Finish&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cs7s768ztichoad98zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cs7s768ztichoad98zs.png" alt="Image description" width="669" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; You can find the exported certificate at the specified location in the file system.&lt;/p&gt;




&lt;p&gt;=&amp;gt; Verify Certificate : &lt;/p&gt;

&lt;p&gt;-&amp;gt; Open manage computer certificates&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8e4i5mamgof2z9yg7xw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc8e4i5mamgof2z9yg7xw.png" alt="Image description" width="473" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Navigate to the 'Trusted Root Certification Authorities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Right click on Certificates tab&lt;br&gt;
All task&amp;gt;&amp;gt; import&lt;br&gt;
Local machine should be checked &amp;gt;&amp;gt; Next &lt;br&gt;
Click on browse button &lt;br&gt;
Select certificate from location where you previously stored &lt;br&gt;
Place all ecrtifcates in following store should be enable&lt;br&gt;
Next &amp;gt;&amp;gt; Finish&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;-&amp;gt; Now, go back to 'Manage User Certificates' and check that the certificate is verified&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3ew3mwibae3kit1c7qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3ew3mwibae3kit1c7qj.png" alt="Image description" width="753" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; Sign our app using created certificate :&lt;/p&gt;

&lt;p&gt;-&amp;gt; add below code in .csproj file &lt;/p&gt;

&lt;p&gt;// Replace your_certificate_thumbprint with the previously created thumbprint, which was generated in PowerShell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4punrlxol5zg1n4n7dot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4punrlxol5zg1n4n7dot.png" alt="csproj file" width="800" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; update below code in .csproj file&lt;/p&gt;

&lt;p&gt;// comment this tags for production mode of app &lt;br&gt;
// Update the App ID to the appropriate name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn60axr0ut3xzy9i0d0k4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn60axr0ut3xzy9i0d0k4.png" alt="csproj file" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;=&amp;gt; Configure resources for production app:&lt;/p&gt;

&lt;p&gt;-&amp;gt; Create an 'Assets' folder and place all resources in it. Please check this &lt;a href="https://github.com/JayMalli/groot_app-maui-windows_publish/tree/main" rel="noopener noreferrer"&gt;repository&lt;/a&gt; for reference. &lt;/p&gt;

&lt;p&gt;-&amp;gt; Modify Package.appxmanifest file with below code.&lt;/p&gt;

&lt;p&gt;// Replace 'Your_Name' with the name you want to display as the publisher in the app.&lt;/p&gt;

&lt;p&gt;// Replace 'Your_thumbprint_Name' with the name which was provided in the creation of thumprint. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folsgo23gd9cvtqst5uoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folsgo23gd9cvtqst5uoa.png" alt="csproj code" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tfdkj4teiyysicd3vbc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tfdkj4teiyysicd3vbc.png" alt="csproj code" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Reference this &lt;a href="https://github.com/JayMalli/groot_app-maui-windows_publish/tree/main" rel="noopener noreferrer"&gt;repo&lt;/a&gt; for code. &lt;/p&gt;

&lt;p&gt;=&amp;gt; Publish App: &lt;/p&gt;

&lt;p&gt;-&amp;gt; execute following command in the terminal&lt;/p&gt;

&lt;p&gt;// Replace 'your_certificate_thumbprint' with the previously created thumbprint, which was generated in PowerShell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;dotnet build  /restore /t:publish /p:TargetFramework=net7.0-windows10.0.19041.0 /p:configuration=release /p:GenerateAppxPackageOnBuild=true /p:AppxPackageSigningEnabled=true /p:PackageCertificateThumbprint=your_certificate_thumbprint&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldkdtzazkwlflf1ucene.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldkdtzazkwlflf1ucene.png" alt="published app msix package" width="800" height="45"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;-&amp;gt; Open the MSIX package in the file storage and launch it&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8irxc5riic1y4qsxm8ib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8irxc5riic1y4qsxm8ib.png" alt="installation wizard" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Install app&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc2p0wfu38jatztqos2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc2p0wfu38jatztqos2a.png" alt="app section" width="454" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Launch app&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;=&amp;gt; finally Groot is here! :)  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6q1036pkhbpvap7w5ru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6q1036pkhbpvap7w5ru.png" alt="production app" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; The example above demonstrates how to develop &amp;amp; publish .NET maui app for windows platform ,  you can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;a href="https://github.com/JayMalli/groot_app-maui-windows_publish/tree/main" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on LinkedIn. You can find me at &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>windows</category>
      <category>publish</category>
    </item>
    <item>
      <title>Gsuit/Google Drive API integration in .NET MAUI</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Sun, 22 Oct 2023 09:50:52 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/gsuitgoogle-api-integration-in-net-maui-3kch</link>
      <guid>https://forem.com/jaymalli_programmer/gsuitgoogle-api-integration-in-net-maui-3kch</guid>
      <description>&lt;p&gt;Hello Developers, we had implemented google OAuth 2.0 service for authoring user with google sign in the previous blog.&lt;/p&gt;

&lt;p&gt;If you couldn't get the chance to check that blog then check it out, Hurry up! I'm waiting for you here!!!&lt;br&gt;&lt;br&gt;
=&amp;gt; &lt;a href="https://dev.to/jaymalli_programmer/google-oauth-20-authorization-service-implementation-in-net-maui-okl"&gt;Previous Blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; A Quick flashback of our previous Blog :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Developer Account &amp;gt;&amp;gt; Create Oauth Client Id &amp;gt;&amp;gt; implement OAuth service &amp;gt;&amp;gt; Get the access__token &amp;amp; refresh_token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, It's time to use that tokens in some useful way. So we are integrating drive API in our app to get the user's drive data.&lt;/p&gt;

&lt;p&gt;So, First enable the Google Drive API from the API Services of &lt;a href="https://console.cloud.google.com" rel="noopener noreferrer"&gt;Google cloud console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22yte2f5ehjj7893yp7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22yte2f5ehjj7893yp7y.png" alt="API Services" width="454" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2heafouev16fhh85ac9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2heafouev16fhh85ac9c.png" alt="Library" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g4um77ccjbtx1lykjou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1g4um77ccjbtx1lykjou.png" alt="Google Drive API" width="583" height="223"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that go to &lt;a href="https://admin.google.com" rel="noopener noreferrer"&gt;Admin Console&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go to Security -&amp;gt; Access And Data Control -&amp;gt; API controls -&amp;gt; Domain-Wide delegation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1ovw61i32hqik7ivlbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1ovw61i32hqik7ivlbp.png" alt="Image description" width="303" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add new API Client by click on "Add new"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm56yt1dea947y1xp6gm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm56yt1dea947y1xp6gm8.png" alt="Admin Console" width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide the Client Id which was created previously which can be get from &lt;a href="https://console.cloud.google.com" rel="noopener noreferrer"&gt;Google cloud console&lt;/a&gt; under the credentails &amp;gt; Oauth Client Id tab.&lt;/p&gt;

&lt;p&gt;If you want to know that how to create client Id then check this previous &lt;a href="https://dev.to/jaymalli_programmer/google-oauth-20-authorization-service-implementation-in-net-maui-okl"&gt;blog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And provide this Oauth Scope for Drive access: &lt;/p&gt;

&lt;p&gt;Authorize the provided client Id to access the google drive. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z1ebqsxgm9tsi4so3bd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5z1ebqsxgm9tsi4so3bd.png" alt="Admin Console" width="475" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Makke sure  that Drive &amp;amp; Docs Service is ON from the &lt;a href="https://admin.google.com/" rel="noopener noreferrer"&gt;Admin  Console&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnw2706g5clsuaocqlqg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnw2706g5clsuaocqlqg.png" alt="Admin Console" width="542" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's time to do some code!&lt;/p&gt;

&lt;p&gt;=&amp;gt; Required Nuget Packages :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Google.Apis" rel="noopener noreferrer"&gt;Google.Apis --v 1.60&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Google.Apis.Auth" rel="noopener noreferrer"&gt;Google.Apis.Auth --v 1.60&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nuget.org/packages/Google.Apis.Drive.v3" rel="noopener noreferrer"&gt;Google.Apis.Drive.v3 --v 1.60&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.nuget.org/packages/Google.Apis" rel="noopener noreferrer"&gt;Google.Apis --v 1.60&lt;/a&gt;
-&lt;a href="https://www.nuget.org/packages/Newtonsoft.Json" rel="noopener noreferrer"&gt;Newtonsoft.Json&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic code for the Auth serivce is available in the below repository which we had done in previous blog.&lt;br&gt;
&lt;a href="https://github.com/JayMalli/Google_OAuth2.0-MAUI" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here , we are implementing the feature of getting files from the drive which contains specific keywords.&lt;/p&gt;

&lt;p&gt;=&amp;gt; code for Drive.cs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Google.Apis.Drive.v3;
using File = Google.Apis.Drive.v3.Data.File;

namespace gsuit_api_maui
{

    public class Drive
    {
        public Drive()
        {
        }

        public List&amp;lt;File&amp;gt; GetDriveFiles(string[] keywords, DriveService driveService)
        {
            List&amp;lt;File&amp;gt; Files = new List&amp;lt;File&amp;gt;();
            foreach (string keyword in keywords)
            {
                try
                {
                    FilesResource.ListRequest files = driveService.Files.List();
                    files.Fields = "nextPageToken,files(id,name,mimeType,webViewLink,permissions)";
                    files.Corpora = "user";
                    files.IncludeItemsFromAllDrives = true;
                    files.IncludeTeamDriveItems = true;
                    files.SupportsAllDrives = true;
// filter for  files which contains specific keyword
                    files.Q = $"fullText contains '\"{keyword}\"'";
                    List&amp;lt;File&amp;gt; result = (List&amp;lt;File&amp;gt;)files.Execute().Files;
                    Files.AddRange(result);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

            }
            return Files;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/JayMalli/GSuit_Drive-MAUI/tree/main" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on LinkedIn. You can find me at &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>maui</category>
      <category>google</category>
      <category>api</category>
    </item>
    <item>
      <title>Implement Logging Service in .NET MAUI</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Thu, 19 Oct 2023 03:37:19 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/implement-logging-service-in-net-maui-2ne1</link>
      <guid>https://forem.com/jaymalli_programmer/implement-logging-service-in-net-maui-2ne1</guid>
      <description>&lt;p&gt;Hello Devs, In this blog we'll learn that how to implement file based Logging service in .NET MAUI without using any external library/packages.&lt;/p&gt;

&lt;p&gt;First of all, add below code in the MainPage.xaml.cs file which is located at the root of project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System.Diagnostics;
using System.Reflection;
using System.Runtime.ExceptionServices;
namespace logs;

public partial class MainPage : ContentPage
{

    public MainPage()
    {
        InitializeComponent();

        AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
    }

    private void CurrentDomain_FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
    {
        if (e.Exception is Exception ex)
        {
            LogException(ex);
        }
    }

    private void LogException(Exception ex)
    {
        string logsFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app_logs.txt");
        try
        {
            // Get information about the method that caused the exception.        
            StackFrame sf = new StackFrame(1);
            MethodBase method = sf.GetMethod();
            int line = sf.GetFileLineNumber();

            // Create a log message with thread ID, timestamp, method information, and the exception message.

            string exMsg = Environment.CurrentManagedThreadId + " ~ " + DateTime.Now.ToString() + method.DeclaringType.FullName + "::" + line + "-" + GetErrorMessage(ex);

            // Append the log message to the specified log file.
            File.AppendAllText(logsFilePath, exMsg + Environment.NewLine + Environment.NewLine);
        }
        catch (Exception exe)
        {
            // If there's an exception while logging, write the error message to the console.
            Console.WriteLine(exe.Message);
        }
    }

    private string GetErrorMessage(Exception ex)
    {
        if (ex != null)
        {
            string message = ex.StackTrace + " " + ex.Message;
            if (ex.InnerException != null)
            {
                message += "~ Caused By" + GetErrorMessage(ex.InnerException);
            }
            return message;
        }

        return string.Empty;
    }

}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Let's Understand above Code.&lt;/p&gt;

&lt;p&gt;=&amp;gt; AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This line subscribes the CurrentDomain_FirstChanceException method to the FirstChanceException event of the current application domain. This event is raised whenever an exception is thrown in the application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;=&amp;gt; LogException(Exception ex) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This method is responsible for logging exceptions to a file.&lt;/li&gt;
&lt;li&gt;It constructs a log file path (logsFilePath) within the application's base directory and attempts to log the exception information to it.&lt;/li&gt;
&lt;li&gt;It uses StackFrame to retrieve information about the method that caused the exception, such as the method name and line number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;=&amp;gt; GetErrorMessage(Exception ex):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is a recursive method that retrieves the error message and stack trace for an exception, including any inner exceptions.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;After that use try catch block in the code where you want to get logs if any exception occurs. here we add try-catch block in the MainPage.xaml.cs file for demonstrate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private void OnCounterClicked(object sender, EventArgs e)
    {
        throw new Exception("Counter Button Clicked!");
    }

    private void OnGetFilesClicked(object sender, EventArgs e)
    {
        try
        {
               // there is no any directory exist , so exception will be occured
            string path = "/demo_logs";
            Directory.GetFiles(path);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;=&amp;gt; Logging file location (according to specified path in the code):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for windows --&amp;gt;  bin\Debug\net7.0-windows10.0.19041.0\win10-x64\app_logs.txt&lt;/li&gt;
&lt;li&gt;for MacOs --&amp;gt; bin/Debug/net7.0-maccatalyst/maccatalsyt-x64/[Project_Name].app/Contents/MonoBundle/app_logs.txt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;=&amp;gt; Logs : &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hqpy8pkj2pwxhkjgj64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hqpy8pkj2pwxhkjgj64.png" alt="logs file output" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;You can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/JayMalli/MAUI-Logging" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on LinkedIn. You can find me at &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>maui</category>
      <category>dotnet</category>
      <category>logs</category>
    </item>
    <item>
      <title>Google OAuth 2.0 authorization service implementation in .NET MAUI</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Wed, 18 Oct 2023 06:14:52 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/google-oauth-20-authorization-service-implementation-in-net-maui-okl</link>
      <guid>https://forem.com/jaymalli_programmer/google-oauth-20-authorization-service-implementation-in-net-maui-okl</guid>
      <description>&lt;p&gt;Hello Programmers!, So today we'll learn that how to implement google OAuth 2.0  authorization service &amp;amp; integrate in .NET MAUI using WebView controls.&lt;/p&gt;

&lt;p&gt;Step - 1 : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create your Google Developer Account at &lt;a href="https://developers.google.com/" rel="noopener noreferrer"&gt;Google Developer&lt;/a&gt;  , if you han't any!  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;a href="https://console.cloud.google.com/" rel="noopener noreferrer"&gt;Cloud Console&lt;/a&gt; and click on DropDown menu then create on "New Project".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4n9qruel8wbk4esiu9l0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4n9qruel8wbk4esiu9l0.png" alt="create new project" width="800" height="113"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step - 2 :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to "API &amp;amp; Services Tab by Clicking on menu icon of current project then Click on "Credentials".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qup7ct3840i66h9u8hr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qup7ct3840i66h9u8hr.png" alt="Credentials" width="442" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on "Create Credentials" Button and select "Oauth client ID" option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhvcai331zrv4kdw79pf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyhvcai331zrv4kdw79pf.png" alt="Oauth Client ID" width="440" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step - 3 : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select Application Type accroding to your requirement &amp;amp; provide Name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmsoeeis20hxrcron0zt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmsoeeis20hxrcron0zt.png" alt="App Info" width="700" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add redirect URI for app on which user is redirected after authorized.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u8cbrkprj40h99igp7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u8cbrkprj40h99igp7j.png" alt="Redirect URI" width="743" height="577"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on "Create Button"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get the Client Id &amp;amp; Client Secrets.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F226geasdwykrd1mfjuha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F226geasdwykrd1mfjuha.png" alt="Client Id &amp;amp; Secrets" width="526" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;STEP - 4 :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup basic maui App.&lt;/li&gt;
&lt;li&gt;create authorization service &amp;amp; add below code.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App
{
    public static class Auth
    {
        public static string ConstructGoogleSignInUrl()
        {
            // Specify the necessary parameters for the Google Sign-In URL
            const string clientId = "your_app_clientId";

            const string responseType = "code";
            const string accessType = "offline";
            const string redirect_uri = "your_app_redirect_uri";

            // Construct the Google Sign-In URL
            return "https://accounts.google.com/o/oauth2/v2/auth" +
                            $"?client_id={Uri.EscapeDataString(clientId)}" +
                            $"&amp;amp;redirect_uri={Uri.EscapeDataString(redirect_uri)}" +
                            $"&amp;amp;response_type={Uri.EscapeDataString(responseType)}" +
                            $"&amp;amp;scope={Uri.EscapeDataString(scope)}" +
                            $"&amp;amp;access_type={Uri.EscapeDataString(accessType)}" +
                            "&amp;amp;include_granted_scopes=true" +
                            "&amp;amp;prompt=consent";


        }


    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step - 5 : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load webview with the google login screen on button click.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private void OnGetFilesClicked(object sender, EventArgs e)
    {
        WebView _signInWebView = new WebView
        {
            Source = Auth.ConstructGoogleSignInUrl(),
            VerticalOptions = LayoutOptions.Fill,
        };

    }
ContentPage signInContentPage = new ContentPage
        {
            Content = grid,
        };

        try
        {
            Application.Current.MainPage.Navigation.PushModalAsync(signInContentPage);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step - 6 : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Google Login screen is visible after clicked on button. Fill the user email id &amp;amp; password, Click "Allow" button on oauth consent scrren.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsado25jourmt6qslx6zc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsado25jourmt6qslx6zc.png" alt="Image description" width="617" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you will get the code in the redirect URL if user authorized successfully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06qpmdvmr8jd3xjdagd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06qpmdvmr8jd3xjdagd0.png" alt="Image description" width="734" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step - 7 :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Get access token from the code which was provided by URL after authorizing a user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add required nuget packages 
1) &lt;a href="https://www.nuget.org/packages/Newtonsoft.Json" rel="noopener noreferrer"&gt;Newtonsoft&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; public static (string, string) ExchangeCodeForAccessToken(string code)
        {

            // Configure the necessary parameters for the token exchange
            const string clientId = "your_app_client_id";
            const string clientSecret = "your_app_client_secret";
            const string redirectUri = "your_app_redirect_URI";

            // Create an instance of HttpClient
            using (HttpClient client = new HttpClient())
            {
                // Construct the token exchange URL
                const string tokenUrl = "https://oauth2.googleapis.com/token";

                // Create a FormUrlEncodedContent object with the required parameters
                FormUrlEncodedContent content = new FormUrlEncodedContent(new Dictionary&amp;lt;string, string&amp;gt;
              {
                   { "code", code },
                   { "client_id", clientId },
                   { "client_secret", clientSecret },
                   { "redirect_uri", redirectUri },
                   { "grant_type", "authorization_code" }
              });

                // Send a POST request to the token endpoint to exchange the code for an access token
                HttpResponseMessage response = client.PostAsync(tokenUrl, content).Result;

                // Check if the request was successful
                if (response.IsSuccessStatusCode)
                {
                    // Read the response content
                    string responseContent = response.Content.ReadAsStringAsync().Result;

                    // Parse the JSON response to extract the access token
                    JObject json = JObject.Parse(responseContent);
                    string accessToken = json.GetValue("access_token").ToString();
                    string refreshToken = json.GetValue("refresh_token").ToString();
                    return (accessToken, refreshToken);
                }
                else
                {
                    // Exception:  "Token exchange request failed with status code: {response.StatusCode}"
                }
            }

            return (null, null);
        }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Get the access_token &amp;amp; refresh_token which is used to create credentials for access services API such as Drive , Gmail API etc.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// other code...

_signInWebView.Navigating += (sender, e) =&amp;gt;
       {

           string code = Auth.OnWebViewNavigating(e, signInContentPage);
           if (e.Url.StartsWith("http://localhost") &amp;amp;&amp;amp; code != null)
           {

               (string access_token, string refresh_token) = Auth.ExchangeCodeForAccessToken(code);
           }

       };

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/JayMalli/Google_OAuth2.0-MAUI" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on &lt;a href="https://www.linkedin.com/in/jaymalli" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. You can find me at LinkedIn&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>oauth</category>
      <category>api</category>
    </item>
    <item>
      <title>Extract Content From ODF files using C#</title>
      <dc:creator>Jay Malli</dc:creator>
      <pubDate>Sun, 10 Sep 2023 12:08:06 +0000</pubDate>
      <link>https://forem.com/jaymalli_programmer/extract-content-from-odf-files-using-c-132b</link>
      <guid>https://forem.com/jaymalli_programmer/extract-content-from-odf-files-using-c-132b</guid>
      <description>&lt;p&gt;Basically, LibreOffice is a popular open-source office suite that provides users with the ability to create and edit documents, presentations, spreadsheets, and more. Files created with LibreOffice often have the Open Document Format (ODF) extension, such as .odt for text documents and .ods for spreadsheets.&lt;/p&gt;

&lt;p&gt;Our requirement is to open this type of ODF files &amp;amp; extract content from it. We can't extract content from ODF files directly using File Class of IO namespace, then what should do?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5unmtvnxrfesiv8sd07z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5unmtvnxrfesiv8sd07z.jpg" alt=" ODF documents are stored in XML based format " width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yaa right! if could get the data of file in XML then we can also get content from that XML data. Let's take example for Word doc file [LibreOffice Writer] which have extension ".odt" . &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 1&lt;/strong&gt; : Get the ODF file data in XML format&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvnn00ltsitf6oxzilqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvnn00ltsitf6oxzilqs.png" alt="Code for get XML data from file with o/p" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 2&lt;/strong&gt;: Filter out all .xml files &amp;amp; get xml data. "content.xml" file contains the content/text of file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuakvk9m8lved6v2xvkca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuakvk9m8lved6v2xvkca.png" alt="filter out content.xml file with o/p" width="800" height="293"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step - 3&lt;/strong&gt; : Now Extract text content from XML data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvwhewrx2m97qeovxwvf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvwhewrx2m97qeovxwvf.png" alt="Code for extract text from XML data with o/p" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;strong&gt;Source Code&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using System.IO.Compression;
using System.Xml;

namespace Content
{
    public class ODF
    {
        public  string ReadText(string filePath)
        {
            string textContent = "";

            using (ZipArchive zipArchive = ZipFile.OpenRead(filePath))
            {
                foreach (var entry in zipArchive.Entries)
                    {
                        if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
                        {
                            using StreamReader reader = new StreamReader(entry.Open());
                            string xmlContent = reader.ReadToEnd();
                          textContent += ExtractTextFromXml(xmlContent);  
                        }
                    }
            }

            return textContent; // output : text content 
        }

        public string ExtractTextFromXml(string xmlContent)
        {
            string textContent = "";

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlContent);

            // add required namespace for different types of documents
            XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDoc.NameTable);
            nsManager.AddNamespace("text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0"); // for doc files with extension .odt   
            nsManager.AddNamespace("office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0"); // comman for all ODF files  

            foreach (XmlNode node in xmlDoc.SelectNodes("//text:p | //text:h", nsManager))
            {
                textContent += node.InnerText + Environment.NewLine;
            }

            return textContent;
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;=&amp;gt; The example above demonstrates how to extract text content from a .odt document file. If you would like to extract content from other ODF files, such as spreadsheets (.ods) or presentations/ppt (.ods), you can find the necessary code in the repository mentioned below.&lt;/p&gt;

&lt;p&gt;=&amp;gt; &lt;a href="https://github.com/JayMalli/Content_Extractor" rel="noopener noreferrer"&gt;GithHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for joining me on this journey of discovery and learning. If you found this blog post valuable and would like to connect further, I'd love to connect with you on LinkedIn. You can find me at &lt;a href="https://www.linkedin.com/in/jaymalli/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have thoughts, questions, or experiences related to this topic, please drop a comment below.&lt;/p&gt;

</description>
      <category>odf</category>
      <category>csharp</category>
      <category>libreoffice</category>
      <category>xml</category>
    </item>
  </channel>
</rss>
