Proxy guides
DigitalOcean App Platform
Route the ProductLense script and events to a small service component on DigitalOcean App Platform, which forwards them with the visitor's real IP address.
App Platform routes paths only to components of your own app, not to another domain. If your app already has a service component (a Node, Python, PHP or Go server), add the routes there with the guide for your framework: Next.js, Flask, FastAPI, Laravel or PHP. For an app made only of a static site, add the small service below.
A proxy service
App Platform puts the visitor's address in the do-connecting-ip header.
import http from 'node:http';
const KEY = process.env.PL_API_KEY;
let script = { body: '', at: 0 };
http
.createServer(async (req, res) => {
try {
if (req.method === 'GET' && req.url === '/js/a.js') {
if (Date.now() - script.at > 3_600_000) {
const r = await fetch('https://api.productlense.app/a.js');
if (r.ok) script = { body: await r.text(), at: Date.now() };
}
if (!script.body) return res.writeHead(502).end();
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8', 'Cache-Control': 'public, max-age=86400' });
return res.end(script.body);
}
if (req.method === 'POST' && req.url === '/m/api/e' && KEY) {
let text = '';
for await (const chunk of req) {
text += chunk;
if (text.length > 8192) return res.writeHead(413).end();
}
const body = JSON.parse(text);
const extra = { ip: req.headers['do-connecting-ip'] ?? '', ua: req.headers['user-agent'] ?? '' };
const out = body.b ? { ...body, b: body.b.map((e) => ({ ...e, ...extra })) } : { ...body, ...extra };
const r = await fetch('https://api.productlense.app/api/v1/events', {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(out),
});
return res.writeHead(r.status).end();
}
res.writeHead(404).end();
} catch {
res.writeHead(400).end();
}
})
.listen(process.env.PORT || 8080);services:
- name: productlense-proxy
source_dir: productlense-proxy
run_command: node server.js
http_port: 8080
instance_size_slug: apps-s-1vcpu-0.5gb
envs:
- key: PL_API_KEY
type: SECRET
scope: RUN_TIME
ingress:
rules:
- match: { path: { prefix: /js/a.js } }
component: { name: productlense-proxy, preserve_path_prefix: true }
- match: { path: { prefix: /m/api/e } }
component: { name: productlense-proxy, preserve_path_prefix: true }
- match: { path: { prefix: / } }
component: { name: web }preserve_path_prefix: true passes the full path to the service, which the routes above expect. Replace web with the name of your existing component. PL_API_KEY is optional: without it the service serves only the script.
Use it
<script defer data-site="SITE_ID" data-api="https://api.productlense.app" src="/js/a.js"></script><script defer data-site="SITE_ID" data-api="https://example.com/m" src="/js/a.js"></script>Check it works
- After the deploy finishes, open
https://example.com/js/a.js: it returns JavaScript. - Load a page with the developer tools open. The Network tab shows a
POSTtoapi/ewith status204. - Open the Live page in the ProductLense dashboard. Your visit appears within a few seconds.