Skip to content

bug: Share Sheet Markdown Badge URL Corrupted by String.replace Matching Domain Substring #8268

Description

@ionfwsrijan

Description

The handleCopyMarkdown function in ShareSheet.tsx constructs the badge URL using:

PROFILE_URL(username).replace(username, `api/streak?user=${username}`)

PROFILE_URL(username) returns https://commitpulse.vercel.app/dashboard/{username}. String.prototype.replace() replaces the first occurrence of the substring. If the username happens to appear as a substring of the domain name (e.g., "vercel", "commit", "pulse", "app"), the replace hits the domain instead of the path segment, producing a completely broken URL.

Concrete example: Username = vercel

  • PROFILE_URL("vercel")https://commitpulse.vercel.app/dashboard/vercel
  • .replace("vercel", "api/streak?user=vercel")https://commitpulse.api/streak?user=vercel.app/dashboard/vercel

The result is a nonsensical URL pointing to commitpulse.api (a non-existent domain) with the path mangled. The user copies this broken markdown and pastes it into their README, where the badge image silently fails to load.

The same corruption happens for any username that is a substring of commitpulse.vercel.app: commit, pulse, app, vercel, commitpulse, etc.

Affected Files

  • components/dashboard/ShareSheet.tsx (line 173 — handleCopyMarkdown)

Expected Behaviour

The markdown snippet should always produce a valid badge URL:

![CommitPulse](https://commitpulse.vercel.app/api/streak?user=<username>)

Actual Behaviour

For usernames that are substrings of the domain name (commitpulse.vercel.app), the String.replace matches the domain instead of the path segment, producing a mangled URL like:

![CommitPulse](https://commitpulse.api/streak?user=vercel.app/dashboard/vercel)

The badge image fails to load in the user's README, and the copied markdown is broken. There is no error feedback — the copy succeeds, the user pastes it, and only discovers the issue when the badge doesn't render.

Proposed Fix

Replace the fragile String.replace with explicit URL construction using a known base URL and encodeURIComponent:

// components/dashboard/ShareSheet.tsx — replace handleCopyMarkdown

const BADGE_BASE = 'https://commitpulse.vercel.app/api/streak';

const handleCopyMarkdown = async () => {
  setOptionState('markdown', 'loading');
  try {
    // Construct the badge URL directly from a constant base URL,
    // avoiding any string-matching on the profile URL that could
    // accidentally hit the domain name.
    const badgeUrl = `${BADGE_BASE}?user=${encodeURIComponent(username)}`;
    const markdown = `![CommitPulse](${badgeUrl})`;
    await navigator.clipboard.writeText(markdown);
    setOptionState('markdown', 'success');
    setTimeout(() => onClose(), 800);
  } catch {
    setOptionState('markdown', 'error');
  }
};

Additionally, since BADGE_BASE is now a constant, it should be extracted to a shared constants file (e.g., lib/constants.ts) so it can be reused across the app:

// lib/constants.ts
export const SITE_URL = 'https://commitpulse.vercel.app';
export const BADGE_BASE_URL = `${SITE_URL}/api/streak`;

This also handles edge-case usernames with special characters (e.g., usernames containing &, ?, #) by using encodeURIComponent, which the original String.replace approach did not account for.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions