DEV Community

Cover image for šŸ“Ø Pop-out Messaging Extension: Dev Whisper
Anna Villarreal
Anna Villarreal Subscriber

Posted on

šŸ“Ø Pop-out Messaging Extension: Dev Whisper

DEV Weekend Challenge: Community

This is a submission for the DEV Weekend Challenge: Community

This is the fastest I've ever made something functional.

- b r e a t h e s -

The Community

For the past couple years, I have actively used dev as a resource. Learning, discovering, and trying things way out of my league. Because of this, I have learned more than I would have on my own with no other support. When you enter the realm of web development and you don't really have anyone close to you doing it, it's the wild west. Dev has provided a place for all walks of life to learn and push forward to discover new things. For this reason, I chose Dev as my community!

The one thing I noticed about dev is there there was no p2p chat. I thought it would be cool if users could send simple messages together about cool projects, ect. If the chat is implemented by way of an optional extension, then we are never forcing people to deal with the chat. It's truly a personal decision.

Thinking from a community standpoint - I wanted to include all the meme monday memes inside the gif picker in the chat. YES you can probably find your meme you posted 2 months ago. This way, I'm using community data, for the community, to continue enhancing the experience.


What I Built

A free open source messaging app for dev members to communicate with each other without having to share personal information. It's shipped as an extension but has a pop-out option for ease of use.


Demo


Code

GitHub logo AnnaVi11arrea1 / dev_messages

3rd party messaging app that works with Dev.to

Dev.to Messages (Unofficial 3rd Party Extension)

A Chrome extension that adds private direct messaging to Dev.to, with built-in link safety, spam reporting, and a first-contact approval system.


File Structure

dev_messages/
ā”œā”€ā”€ manifest.json          MV3 manifest
ā”œā”€ā”€ background.js          Service worker – updates unread badge
ā”œā”€ā”€ content.js             Injected into dev.to – detects user, adds Message buttons
ā”œā”€ā”€ popup.html             All UI views & modals
ā”œā”€ā”€ popup.css              Dev.to-inspired styles (380px popup)
ā”œā”€ā”€ popup.js               Full SPA app logic
ā”œā”€ā”€ js/
│   ā”œā”€ā”€ storage.js         chrome.storage.local wrapper
│   ā”œā”€ā”€ eligibility.js     Dev.to API eligibility check
│   └── linkSafety.js      URL spoofing detection + safe renderer
└── icons/                 16 / 48 / 128 px PNG icons

Features

Feature Details
Account eligibility Checks joined_at & articles_count via the Dev.to public API. Account must be at least 30 days old and have at least 1 published post before sending or receiving messages.
First-contact approval When someone messages you for the first time,
…

Meme Search Implementation

Arguably, the most important feature. XD

I wanted not to just pull in the memes, but to allow a user to search them. The best way I figured was to use the alt tags as search parameters. Many of us are nice enough to put alt tags on our stuff, and so I was able to leverage that to get a sort of lazy search going. Hey, it works. I thought that was a little cool and different so that is the snippet I'll be sharing below:

/* gifPicker.js – Fetches meme images from Ben Halpern's Meme Monday posts,
   including every image posted in the comments (community memes). */

const GifPicker = {
  _cache: null,

  async fetchImages() {
    if (this._cache) return this._cache;

    /* ── Step 1: collect all Meme Monday articles ─────────────────────────── */
    const articles = [];
    for (let page = 1; page <= 6; page++) {
      try {
        const res = await fetch(
          `https://dev.to/api/articles?username=ben&per_page=100&page=${page}`
        );
        if (!res.ok) break;
        const data = await res.json();
        if (!data.length) break;
        for (const a of data) {
          if (/meme\s+monday/i.test(a.title)) articles.push(a);
        }
        if (data.length < 100) break;
      } catch { break; }
    }

    /* ── Step 2: collect images with dedup ────────────────────────────────── */
    const seen   = new Set();
    const images = [];
    const add    = (url, title) => {
      if (!url || seen.has(url)) return;
      seen.add(url);
      images.push({ url, title });
    };

    /* Cover images first so they appear at the top of the grid */
    for (const a of articles) {
      if (a.cover_image) add(a.cover_image, a.title);
    }

    /* ── Step 3: fetch all comment threads in parallel ────────────────────── */
    const commentThreads = await Promise.all(
      articles.map(a =>
        fetch(`https://dev.to/api/comments?a_id=${a.id}`)
          .then(r => r.ok ? r.json() : [])
          .catch(() => [])
      )
    );

    for (let i = 0; i < articles.length; i++) {
      this._extractImages(commentThreads[i], articles[i].title, add);
    }

    this._cache = images;
    return images;
  },

  /* Recursively walk comment tree and pull every <img src="…"> with its own alt */
  _extractImages(comments, articleTitle, add) {
    for (const comment of comments) {
      const imgTags = (comment.body_html || '').matchAll(/<img([^>]+)>/gi);
      for (const m of imgTags) {
        const attrs  = m[1];
        const srcM   = attrs.match(/src="([^"]+)"/i);
        const altM   = attrs.match(/alt="([^"]*)"/i);
        if (srcM) {
          const alt = altM?.[1]?.trim() || articleTitle;
          add(srcM[1], alt);
        }
      }
      if (comment.children?.length) {
        this._extractImages(comment.children, articleTitle, add);
      }
    }
  },
};

Enter fullscreen mode Exit fullscreen mode

How I Built It

I very quickly realized that I was going to need a secure database so users could keep their chat history. I'm using Neon's free tier for this project. Give it a whirl! XD. Requests from the extension go to Vercel over HTTPS. Vercel connects to Neon with an encrypted connection. The database is never exposed to the internet, and it is only queried by Vercel's serverless functions.

I used copilot CLI to quickly build up a working template - I would not be able to build something so quickly without it. This is not my first chrome extension, but it is the 'fanciest' one I have made to date.

I tested with my unpacked files in the google chrome extensions. I was lucky enough to test functionality with another dev user. That was amazingly helpful. A big thank you to @trickell for being awesome.

coolest chat ever

I had fun making a settings and options area for users which includes colors and text sizes for increased accessibility.

customize settings


Features:

Created with the mindset of reducing spam and annoyances

  • Account eligibility - Checks joined_at & articles_count via the Dev.to public API. Account must be at least 30 days old and have at least 1 published post before sending or receiving messages.

  • First-contact approval - When someone messages you for the first time, you see a banner with their opening message and must hit Approve or Deny before the conversation unlocks.

  • Flag / report spam - Every incoming message has a 🚩 button. Clicking it opens a reason picker (spam, phishing, harassment, inappropriate, other) and marks the message as reported.

  • Link spoofing detection - Before any link opens, LinkSafety.isSpoofed() compares the visible URL text against the actual href hostname. A red alert is shown if they differ.

  • Link safety warning - Every link in every message routes through a warning modal that displays the full destination URL before opening it in a new tab.

  • Local storage persistence - All conversations, approvals, flags, and user data are stored in chrome.storage.local. The extension must be installed and active to read messages.

  • Unread badge - The extension icon shows a live unread count, updated by the background service worker whenever storage changes

My extension is getting reviewed by google currently... whish me luck! haha

pending review

A user must have the extension to test it. Guess I've made myself a guinea pig haven't I. (hides, šŸ˜…)

Top comments (2)

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Haha! 🤣 Collecting memes from Meme Monday is definitely what all DEV Community users want. Also, this app has robust security. Very cool app!

Collapse
 
annavi11arrea1 profile image
Anna Villarreal

Thank you! That means alot!