Back to projects
Utility / AutomationClient ProjectRecent

CampSight

Ontario Parks campsite availability monitor. A Puppeteer scraper runs on Render.com and sends FCM push alerts to the Flutter app when target campsites open, with watch management and false-positive prevention.

Project Summary

My Role

Full-Stack Developer

Tech Stack

FlutterNode.jsPuppeteerFirebaseFCMRender.com

App visuals

Challenge

Ontario Parks campsite reservations sell out almost instantly. The client wanted real-time notifications the moment a specific campsite became available — not to check manually every hour. The problem: Ontario Parks has no public API. Any solution required scraping their JavaScript-rendered reservation site, detecting genuine availability changes, and delivering push alerts fast enough to be useful. The scraper also needed to run continuously on a free-tier hosting platform without cold-start failures killing the polling cycle.

Solution

I built a Node.js scraper backend deployed on Render.com and a Flutter app for managing campsite watches and receiving push alerts. Scraper architecture: Puppeteer with puppeteer-core + @sparticuz/chromium to avoid binary size issues on Render's serverless environment — standard Puppeteer's full Chromium build is too large to deploy there. The scraper polls target campsites on a scheduled interval, compares current availability against the last known state stored in Firestore, and triggers FCM push notifications only when a genuine change is detected. A self-ping mechanism keeps the Render.com free-tier service alive between scrape cycles to prevent cold start delays. Firebase Auth token verification is enforced on all backend API endpoints. False-positive problem and fix: Early in production, users received alerts for campsites that were not actually available. Root cause: empty siteNumber fields in the scraped HTML were passing the availability check. Fixed by adding strict validation — only records with a non-empty, valid siteNumber are processed. Second bug — ghost Firestore writes: Puppeteer timeout events were triggering the catch block, which incorrectly wrote an "available" state to Firestore before the page had fully loaded. Fixed by separating timeout handling logic from the availability write path entirely. Flutter app: campsite watch management, FCM notification display, alert history, and Firebase Auth.

Outcome

A live scraper-based alert system that notifies the user within seconds of a campsite opening on Ontario Parks — with false-positive prevention, auth-secured API endpoints, and a Render.com deployment that stays alive between poll cycles. The client left a 5-star review on Upwork: "He understood the problem immediately, handled edge cases I hadn't even thought of, and deployed everything cleanly. Will hire again without hesitation."

What I worked on

Built Node.js scraper with puppeteer-core + @sparticuz/chromium for Render.com serverless compatibility.

Implemented Firestore-backed availability state tracking and FCM push notification dispatch on availability change.

Added Firebase Auth token verification on all backend API routes.

Implemented self-ping keep-alive to prevent Render.com free-tier cold starts between scrape cycles.

Debugged and fixed false-positive alerts caused by empty siteNumber fields passing the availability check.

Debugged and fixed ghost Firestore writes triggered by Puppeteer timeout events hitting the wrong catch block.

Built Flutter app: campsite watch management, alert history, FCM notification display, Firebase Auth integration.