HANDOFF SESSION 109 PULSE R2 SETUP

Documentation for Developers

GoGrab Pulse - Complete Architecture & Operations Guide

Last Updated: February 12, 2026 (Sessions 108-109) Status: Production - Live at pulse.gograb.space


What Is GoGrab Pulse?

GoGrab Pulse is a space-themed web music player built as a static PWA deployed on Cloudflare Pages, with audio files stored on Cloudflare R2 object storage and song metadata managed through both a static JSON file and a NestJS backend module connected to MongoDB.

Live URLs:


Architecture Diagram

SONG SOURCES                  STORAGE                    SERVING
-----------                  -------                    -------
Apple iCloud  ──┐
                ├─ sync CLI ──> Cloudflare R2 ────────> Browser (audio streaming)
Local folder  ──┘    |         (pulse-music bucket)     Free CDN egress
                     |
                     └──> songs.json ──> Cloudflare Pages ──> Browser (player UI)
                     |                    (pulse.gograb.space)
                     └──> MongoDB ──────> Backend API ──────> (future API access)
                          (pulse_songs)   (/api/pulse/*)

Why Cloudflare R2?

We evaluated four storage options for the MP3 files:

Option Pros Cons Verdict
Cloudflare R2 Free egress, CDN-edge delivery, S3-compatible API, scales to thousands of files Needs initial setup, no CORS on r2.dev URLs CHOSEN
Cloudflare Pages Simple (deploy with site), already working 92MB deploys take 80s, 25MB/file limit, doesn't scale past ~100 tracks Used initially, replaced
MongoDB GridFS Already have MongoDB, good for small files Not designed for streaming large audio, adds backend dependency for playback Not suitable for audio
Old FTP server Was working before migration Broke when DNS moved to Cloudflare, no CDN, manual uploads Deprecated

Key R2 benefits:


How Songs Are Stored

Cloudflare R2 Bucket: pulse-music

All 22 MP3 files live in the R2 bucket under the songs/ prefix:

R2 bucket: pulse-music
  songs/A darle, mijo.mp3          (4.1 MB)
  songs/Camine.mp3                 (3.8 MB)
  songs/Dreams.mp3                 (4.2 MB)
  songs/higher-ground.mp3          (5.0 MB)
  ... (22 files, ~93 MB total)

Public URL pattern:

https://pub-5d1dc33f9faa4d5f8bf8aebe972f62de.r2.dev/songs/{filename}.mp3

songs.json (Playlist Manifest)

Lives at deploy/gograb-pulse/music/songs.json and is deployed with the site. Contains the R2 base URL, song metadata, language tags, and genre tags:

{
  "r2BaseUrl": "https://pub-5d1dc33f9faa4d5f8bf8aebe972f62de.r2.dev/songs",
  "songs": [
    {
      "title": "Higher Ground",
      "artist": "GoGrab Space",
      "file": "higher-ground.mp3",
      "lang": "en",
      "genre": "inspirational"
    }
  ]
}

The player reads r2BaseUrl from this file and constructs audio URLs as:

{r2BaseUrl}/{encodeURIComponent(file)}

If r2BaseUrl is missing, it falls back to /music/{file} (same-origin Pages).

MongoDB (Backend)

The NestJS Pulse module stores song metadata in two collections:

This is not yet the primary source for the player (the static songs.json is), but provides the API for future dynamic playlist management.


Are We Still Using FTP?

No. The old FTP-based hosting at gograb.space/pulse is fully deprecated. It broke when DNS moved to Cloudflare nameservers. All songs are now served from Cloudflare R2, and the player UI is deployed via Cloudflare Pages.


How the Player Works

The player is a single HTML file (deploy/gograb-pulse/player/index.html) with inline CSS and JavaScript. No build step, no framework.

Key features:

Why no Web Audio API visualizer? createMediaElementSource() on cross-origin audio (R2) causes the browser to mute all output as a CORS security measure. Instead, we use the gSpace tide SVG animation driven by a JS beat simulator that varies speed and scale with a sinusoidal pattern.


How to Deploy the Player

Quick Deploy (just the UI)

# From the gspace1 workspace root:
yarn deploy:pulse

This deploys deploy/gograb-pulse/ (HTML, CSS, JS, JSON, SVGs) to Cloudflare Pages. Takes about 1-2 seconds since MP3s are not included.

Sync Songs + Deploy

# Sync from iCloud and deploy in one command:
yarn sync:pulse:deploy

Manual Steps

# 1. Copy any new MP3s from iCloud to the deploy music folder
cp ~/Library/Mobile\ Documents/com~apple~CloudDocs/Music/Pulse\ Player\ Songs/*.mp3 \
   deploy/gograb-pulse/music/

# 2. Upload new MP3s to R2 (only new/changed files)
export CLOUDFLARE_ACCOUNT_ID=$(grep CLOUDFLARE_ACCOUNT_ID .env.local | cut -d= -f2)
export CLOUDFLARE_API_TOKEN=$(grep CLOUDFLARE_API_TOKEN .env.local | cut -d= -f2)

npx wrangler r2 object put "pulse-music/songs/My New Song.mp3" \
  --remote --file="deploy/gograb-pulse/music/My New Song.mp3" \
  --content-type="audio/mpeg"

# 3. Add the song to songs.json
# Edit deploy/gograb-pulse/music/songs.json and add:
# { "title": "My New Song", "artist": "Artist Name", "file": "My New Song.mp3", "lang": "en", "genre": "inspirational" }

# 4. Deploy the updated site
yarn deploy:pulse

How to Add New Songs

Step 1: Place the MP3

Drop the MP3 file into your Apple iCloud folder:

~/Library/Mobile Documents/com~apple~CloudDocs/Music/Pulse Player Songs/

Or any local folder.

Step 2: Upload to R2

cd ~/developer/repos-gspace/gspace1
export CLOUDFLARE_ACCOUNT_ID=$(grep CLOUDFLARE_ACCOUNT_ID .env.local | cut -d= -f2)
export CLOUDFLARE_API_TOKEN=$(grep CLOUDFLARE_API_TOKEN .env.local | cut -d= -f2)

npx wrangler r2 object put "pulse-music/songs/Song Title.mp3" \
  --remote \
  --file="/path/to/Song Title.mp3" \
  --content-type="audio/mpeg"

Step 3: Update songs.json

Edit deploy/gograb-pulse/music/songs.json and add to the songs array:

{
  "title": "Song Title",
  "artist": "Artist Name",
  "file": "Song Title.mp3",
  "lang": "en",
  "genre": "worship"
}

Valid lang values: en (English), es (Spanish) Valid genre values: worship, inspirational, latin

Step 4: Deploy

yarn deploy:pulse

Using the Sync Script (All-in-One)

# Sync everything from iCloud, upload to R2, and deploy:
./tools/scripts/sync-pulse-music.sh --source icloud --r2 --deploy

# Dry run first to see what would happen:
./tools/scripts/sync-pulse-music.sh --source icloud --r2 --deploy --dry-run

Key Files Reference

File Purpose
deploy/gograb-pulse/index.html Landing page
deploy/gograb-pulse/player/index.html Player (all features in one file)
deploy/gograb-pulse/music/songs.json Playlist manifest with R2 URL, language, genre
deploy/gograb-pulse/manifest.json PWA manifest
deploy/gograb-pulse/sw.js Service worker (network-first, v4)
deploy/gograb-pulse/gSpace-symbol-white.svg White logo (header, album art)
deploy/gograb-pulse/GoGrab_Space_symbol.svg Full color logo (favicon, PWA icon)
deploy/gograb-pulse/wrangler.toml Cloudflare Pages config
tools/scripts/sync-pulse-music.sh CLI: sync songs from iCloud/local to R2
tools/scripts/deploy-pulse.sh CLI: deploy site to Cloudflare Pages
apis/backend-api/src/app/pulse/ NestJS backend module (MongoDB)
.gitignore MP3s are gitignored: deploy/gograb-pulse/music/*.mp3

Credentials & Config

All credentials live in .env.local (gitignored):

Variable Purpose
CLOUDFLARE_ACCOUNT_ID Cloudflare account for R2 + Pages
CLOUDFLARE_API_TOKEN Token with Pages + R2 + DNS permissions

R2 bucket: pulse-music R2 public URL: https://pub-5d1dc33f9faa4d5f8bf8aebe972f62de.r2.dev/songs/ Custom domain (pending SSL): r2.pulse.gograb.space


Backend API Endpoints (When Running)

Start: yarn serve:backend-api

Endpoint Method Description
/api/pulse/songs GET List all active songs
/api/pulse/songs.json GET Backward-compatible playlist format
/api/pulse/songs POST Register a new song
/api/pulse/songs/bulk POST Bulk upsert songs
/api/pulse/songs/:id PUT Update metadata
/api/pulse/songs/:id DELETE Soft-delete
/api/pulse/songs/:id/play POST Increment play count
/api/pulse/playlists GET/POST List/create playlists
/api/pulse/stats GET Playback statistics

Troubleshooting

Problem Solution
Songs don't play Check browser console. If "outputs zeroes due to CORS", ensure the audio element does NOT have crossorigin="anonymous" and that initAudioContext is NOT called for cross-origin audio
Old version cached The service worker (v4) is network-first. Hard refresh (Cmd+Shift+R) or clear site data in DevTools > Application > Storage
New song not appearing Did you add it to songs.json? Did you redeploy (yarn deploy:pulse)?
R2 upload fails Ensure --remote flag is used with wrangler (npx wrangler r2 object put ... --remote). Without it, files go to local miniflare
Deploy is slow (>10s) MP3s might be in the deploy dir. Check that deploy/gograb-pulse/music/*.mp3 files are gitignored and excluded