Capture Request Referrer via CSS

It all started with this toot, which took me to Herman's blog post outlining a way to use CSS to filter out bot traffic and capture unique views:

In order to do this I use the IP address of the request to determine the country, then hash the IP address along with the date. All subsequent requests to the page are checked for matching IP address + date hashes and duplicates are discarded.

I then stumbled into myfonj's comment on Hacker News.

Have you considered serving actual small transparent image with (private) caching headers set to expire at midnight (and not storing IPs at all)?

This first post will focus on validating whether the referrer can be captured via a css only approach. In a subsequent post, I will focus on using private caching headers as an alternative to IP logging to identify unique views.

On Privacy

I don't want to have to display a privacy banner on the website, and I don't want to be too creepy in tracking of visits, but just creepy enough to be able to sate my vanity and identify where a visit originates from.

In practice, capturing the referrer proves to be more difficult than I thought due to prevalence of noreferrer / noopener links (for good reasons - phishing threats).

On first sight, Plausible seemed to fit the bill, and whilst better than Google Analytics, despite claims to the contrary, I am not confident that it is GDPR compliant (see here for more details).

To steer clear of GDPR I don't want to mess with any IP logging in any capacity. This is why myfonj's suggestion on Hacker News appeals.

Bots and spiders

Server logging casts a wide net, and the logs intermingle human and bot traffic. This is where client-side sifting can come into play, to help isolate the visits that you care about. Where Herman's approach is interesting is that holds out the promise of better filtering of bots. Some crawlers use javascript... some don't. Even less will use a mouse hover, which promises a better signal to noise ratio in the sifting through of visitors.

Proof of concept

To test the behavior of the referral I added a link to my website in my Github profile.

CSS only tracker

In my current setup with Sitepress and Rails I couldn't access @page within the layout component to get the request path, and I don't think there is a way to pass arguments to layouts. Instead, I set up a css class to handle the hover and the get request for the image:

.hit:hover {
  border-image: var(--path);
}

I wrapped the markdown content with a div with a reference to the class and used the style attribute to set the path:

def template
  div(class: ' hit', style: "--path: url('/hit/handle?ref=#{@page.request_path}');") do
    super
  end
end

Using the request path allows me to attribute the particular hit to the specific post.

Hit controller

For now, I am just logging the referrer to validate that the request via css works:

class HitController < ApplicationController
  layout -> { ApplicationLayout }

  skip_before_action :verify_authenticity_token, only: [:handle]

  def handle
    Rails.logger.info "Referrer: #{request.referer}"

    send_data pixel,
      type: "image/gif",
      disposition: "inline",
      status: :ok
  end
end

Clicking on the link in my Github profile, unfortunately, the referrer came through as:

Referrer: https://williamneal.dev/assets/global/styles-30d9ccc1d7be4f6706142f63a1b8489dabcc3371.css 

I tried defining the class in the head of the layout component instead:

style { ".hit:hover { border-image: var(--path);  }" }

This time the referrer came through as:

Referrer: https://williamneal.dev/

Javascript to the rescue

Having no joy with the css only approach, I made a Stimulus controller to handle the hover:

export default class extends Controller {
  referred = false;
  
  connect() {
    this.element.addEventListener('mouseover', () => this.handleHover());
  }

  handleHover() {
    if (this.referred) return;
    const pageUrl = encodeURIComponent(window.location.href);
    const requestUrl = `/hit/handle?ref=${pageUrl}`;

    fetch(requestUrl, {
      method: 'GET',
    }).then(response => {
      if (response.ok) {
        this.referred = true;
      } else {
        console.error('Request failed');
      }
    });
  }
}

Huzzah! The referrer came through as:

Referrer: https://github.com/Sandbagger

Conclusion

So I didn't manage to capture the referrer via a css only approach. Is there something that I am missing? I am all ears if you have any further ideas to get the CSS only approach to work - hit me up!

Using Javascript to handle the hover will allow me to move on to implementing myfonj's suggestion of using a private cache to attribute unique views.

Other writing