tidaldb/applications/forage/js/capture.js
jordan c1c5a10fbc chore: reuse reqwest::Client across requests in forage embedder; minor forage updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 23:16:32 -07:00

98 lines
3.1 KiB
JavaScript

/**
* Forage P1 — Page Signal Capture
*
* Run via Claude's javascript_tool on any web page you want to add to Forage:
*
* javascript_tool({ text: <contents of this file>, tabId: <current tab> })
*
* What it does:
* 1. Reads the page title, URL, word count, and meta description.
* 2. POSTs to /capture — registers the page as a Forage item (idempotent)
* and fires a "view" signal for USER_ID.
* 3. After DWELL_MS milliseconds, fires a "dwell" signal if still on page.
* (The dwell timer is cancelled if the snippet runs again before it fires.)
*
* Configuration:
* Change USER_ID to match the Forage user you are browsing as (1, 2, or 3
* for the seed users; any positive integer for a new user).
* Set TOKEN to the value passed with --token when starting the server,
* or leave empty ('') if the server was started without --token.
*/
(function forageCapture() {
const SERVER = 'http://localhost:4242';
const USER_ID = 1;
const TOKEN = '';
const DWELL_MS = 30_000;
const url = location.href;
const canonicalUrl = document.querySelector('link[rel="canonical"]')?.href || '';
const title = document.title || url;
const words = document.body.innerText.trim().split(/\s+/).length;
const readingTimeMin = Math.max(1, Math.round(words / 200));
const description =
document.querySelector('meta[name="description"]')?.content ||
document.querySelector('meta[property="og:description"]')?.content ||
'';
const source = (() => {
try {
return new URL(url).hostname.replace(/^www\./, '');
} catch {
return '';
}
})();
// Cancel any previous dwell timer from this snippet on the same page.
if (window.__forageCaptureDwellTimer) {
clearTimeout(window.__forageCaptureDwellTimer);
window.__forageCaptureDwellTimer = null;
}
let itemId = null;
const authHeaders = TOKEN ? { Authorization: `Bearer ${TOKEN}` } : {};
fetch(`${SERVER}/capture`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
body: JSON.stringify({
url,
canonical_url: canonicalUrl,
title,
source,
reading_time_min: readingTimeMin,
description,
user_id: USER_ID,
}),
})
.then((r) => r.json())
.then((d) => {
itemId = d.item_id;
console.log(
`[Forage] Captured: "${title}" — id=${itemId}, ~${readingTimeMin}min read`
);
})
.catch((e) => console.warn('[Forage] /capture failed:', e.message));
// Fire dwell signal after DWELL_MS if still on the page.
window.__forageCaptureDwellTimer = setTimeout(() => {
if (itemId == null) return;
fetch(`${SERVER}/signal`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...authHeaders },
body: JSON.stringify({
user_id: USER_ID,
item_id: itemId,
signal_type: 'dwell',
duration_ms: DWELL_MS,
}),
})
.then(() =>
console.log(`[Forage] Dwell: "${title}" (${DWELL_MS / 1000}s)`)
)
.catch(() => {});
window.__forageCaptureDwellTimer = null;
}, DWELL_MS);
return `[Forage] Capturing "${title}" for user ${USER_ID}`;
})();