Profile picture

Online Indicator Implementation

January 6, 2025

20 min read

When designing this portfolio, I thought it would be fun to add a small feature: an online indicator to show visitors if I am currently active. But as I began working on it, I realized that defining "online" was more complicated than I initially expected. Here's how I tackled the challenge and implemented an efficient and accurate online status system.

What Does "Online" Mean?

Before writing any code, I asked myself: What does 'online' mean in this context? Could it mean:

  • Being at my computer?
  • Actively browsing my portfolio website? (No, because I wouldn’t be refreshing the site that often.)
  • Using a messenger platform? (Not relevant for this use case.)

Ultimately, I decided that "online" would mean I was actively using my MacBook. This simplified things while still delivering the intended functionality.

Implementation

I chose a heartbeat approach for this feature, combining several components to keep the solution lightweight and reliable. Here’s the breakdown:

Cloudflare Workers

  1. Worker #1: Heartbeat Tracker

    • Purpose: Updates a "lastHeartbeat" key in a Cloudflare KV (Key-Value) Storage.
    • How it works: This worker exposes a POST endpoint. When called, it updates the "lastHeartbeat" value with the current timestamp.
    worker.js
    export default { async fetch(request, env, ctx) { try { const { method } = request; // Handle POST request to record the heartbeat if (method === 'POST') { const currentTime = Date.now(); await env.HEARTBEAT.put('lastHeartbeat', currentTime); // Store the heartbeat timestamp in KV return new Response(JSON.stringify({ status: 'OK' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } if (method === 'GET') { // Handle GET request to check if the system is online const lastHeartbeat = await env.HEARTBEAT.get('lastHeartbeat'); // Check if lastHeartbeat exists and if it was within the last 10 minutes const isOnline = lastHeartbeat && Date.now() - parseInt(lastHeartbeat) < 600000; return new Response( JSON.stringify({ isOnline }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } } catch (err) { // In a production application, you could instead choose to retry your KV // read or fall back to a default code path. console.error(`KV returned error: ${err}`); return new Response(err.toString(), { status: 500 }); } }, };
  2. Worker #2: Online Status Checker

    • Purpose: Provides a public API endpoint to determine whether I’m online.
    • How it works: Compares the current time with the "lastHeartbeat" value from KV Storage. If the difference is within a specified threshold (e.g., 15 minutes), the endpoint returns isOnline: true; otherwise, it returns false.
    worker.js
    export default { async fetch(request, env, ctx) { try { // Handle GET request to check if the system is online const lastHeartbeat = await env.HEARTBEAT.get('lastHeartbeat'); // Check if lastHeartbeat exists and if it was within the last 10 minutes const isOnline = lastHeartbeat && Date.now() - parseInt(lastHeartbeat) < 600000; return new Response( JSON.stringify({ isOnline: isOnline }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } catch (err) { // In a production application, you could instead choose to retry your KV // read or fall back to a default code path. console.error(`KV returned error: ${err}`); return new Response(err.toString(), { status: 500 }); } }, };

Cron Job

To send regular heartbeats, I used a cron job:

  • Python Script: A small script sends a POST request to the Cloudflare Worker #1 endpoint every 10 minutes.
  • Crontab: The job is scheduled with crontab -e on my MacBook to ensure it runs reliably.

Here’s the Python script:

heartbeat.py
import requests # The URL of your Cloudflare Worker (replace with your worker's URL) HEARTBEAT_URL = 'https://heartbeat.yourname.workers.dev/' # Function to send the heartbeat signal def send_heartbeat(): try: response = requests.post(HEARTBEAT_URL) if response.status_code == 200: print('Heartbeat sent successfully') else: print(f'Failed to send heartbeat: {response.status_code}') except Exception as e: print(f'Error sending heartbeat: {e}') # Run the heartbeat once (no infinite loop) send_heartbeat()

With the crontab file containing the following line:

crontab
*/10 * * * * /usr/bin/python3 /path/to/heartbeat.py

But I worked in a Python environment and also saved the output to a log file, so I used the following line:

crontab
*/10 * * * * /User/Workspace/python/heartbeat/venv/bin/python /User/Workspace/python/heartbeat/heartbeat.py >> /User/Workspace/python/heartbeat/heartbeat.log 2>&1

Frontend Hook

To display the online status, I created a custom React hook. Here’s a simplified version:

use-online-status.tsx
import { useState, useEffect } from 'react'; export function useOnlineStatus() { const [isOnline, setIsOnline] = useState(false); useEffect(() => { const fetchStatus = async () => { const response = await fetch('/api/is-online'); const data = await response.json(); setIsOnline(data.isOnline); }; fetchStatus(); }, []); return isOnline; }
Implementation using Jotai for state management
use-online-status.tsx
'use client' import { useEffect } from 'react' import { useAtom } from 'jotai' import { onlineAtom } from '@/components/modules/heartbeat/store' const useOnlineStatus = () => { const [online, setOnline] = useAtom(onlineAtom) useEffect(() => { // If the status is already set in sessionStorage, do nothing if (online !== null) return const fetchOnlineStatus = async () => { try { const response = await fetch('https://heartbeat-public.yourname.workers.dev/') const data = await response.json() setOnline(data.isOnline) // Update the atom and sessionStorage } catch (error) { console.error('Failed to fetch online status', error) setOnline(false) // Default to offline if API fails } } fetchOnlineStatus() }, [online, setOnline]) return online } export default useOnlineStatus
store.ts
import { atomWithStorage, createJSONStorage } from 'jotai/utils' // Define a custom storage that uses sessionStorage const sessionStorageWithJSON = createJSONStorage(() => sessionStorage) // Create the atom using the custom storage export const onlineAtom = atomWithStorage( 'online-status', null, // Default value sessionStorageWithJSON )

Optimizations

Initially, my naïve solution involved polling the online status API at regular intervals using setInterval. While functional, it introduced several problems:

  1. Resource Intensity:

    • If a visitor left the page open, it could result in hundreds or thousands of API requests per session, driving up cloud costs and straining performance.
  2. Accuracy vs. Efficiency:

    • Frequent polling improved accuracy but wasn’t sustainable.

Final Approach

I optimized the implementation to balance accuracy, performance, and cost:

  • Single API Request Per Session:
    • If the API initially returns isOnline: true, the status is cached for the remainder of the session.
    • If it returns false, the status is checked again on page reloads.

This approach ensures minimal API calls while still providing accurate and meaningful feedback to users.

Reflections

Implementing an online indicator was a rewarding challenge. Beyond the technical details, it made me consider how to balance accuracy, performance, and user experience. While this feature might seem small, it’s a fun way to make my portfolio feel more personal and dynamic.

If you have any questions, feel free to reach out to me on X / Twitter, LinkedIn, or the contact page!