See the best posts from any Bluesky account
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Replace server-side PostHog middleware with client-side posthog-js

Server-side tracking only captured $pageview events. Switching to
posthog-js enables $pageleave, autocapture (clicks, form submissions),
session recordings, and web vitals — all from the client.

- Add posthog-js, initialize from meta tags injected by Edge template
- Expose PostHog API key as Edge global via PostHogProvider
- Identify authenticated users by DID with handle property
- Update CSP: allow script-src/connect-src for ph.btao.org, add worker-src
- Remove PostHog middleware from router stack and delete the file
- Keep posthog-node for server-side engagement events in EngagementController

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+222 -70
-65
app/middleware/posthog_middleware.ts
··· 1 - import { type HttpContext } from '@adonisjs/core/http' 2 - import { type NextFn } from '@adonisjs/core/types/http' 3 - import { getPostHogClient } from '#services/posthog' 4 - 5 - export default class PostHogMiddleware { 6 - async handle(ctx: HttpContext, next: NextFn) { 7 - const output = await next() 8 - 9 - const posthog = getPostHogClient() 10 - if (!posthog) return output 11 - 12 - // Only track HTML page views, not API calls or assets 13 - const method = ctx.request.method() 14 - if (method !== 'GET') return output 15 - 16 - const contentType = ctx.response.getHeader('content-type') 17 - if (typeof contentType === 'string' && !contentType.includes('text/html')) return output 18 - 19 - const url = ctx.request.url(true) 20 - const userAgent = ctx.request.header('user-agent') || '' 21 - 22 - // Real client IP behind Cloudflare 23 - const ip = 24 - ctx.request.header('cf-connecting-ip') || 25 - ctx.request.header('x-forwarded-for')?.split(',')[0]?.trim() || 26 - ctx.request.ip() 27 - 28 - // Use DID for authenticated users, session ID for anonymous visitors 29 - let distinctId: string 30 - let isAuthenticated = false 31 - try { 32 - await ctx.auth.check() 33 - if (ctx.auth.isAuthenticated && ctx.auth.user) { 34 - distinctId = ctx.auth.user.did 35 - isAuthenticated = true 36 - } else { 37 - distinctId = ctx.session.sessionId 38 - } 39 - } catch { 40 - distinctId = ctx.session.sessionId 41 - } 42 - 43 - posthog.capture({ 44 - distinctId, 45 - event: '$pageview', 46 - properties: { 47 - $current_url: `${process.env.APP_URL}${url}`, 48 - $ip: ip, 49 - $raw_user_agent: userAgent, 50 - $referrer: ctx.request.header('referer') || '', 51 - }, 52 - }) 53 - 54 - if (isAuthenticated && ctx.auth.user) { 55 - posthog.identify({ 56 - distinctId: ctx.auth.user.did, 57 - properties: { 58 - handle: ctx.auth.user.handle, 59 - }, 60 - }) 61 - } 62 - 63 - return output 64 - } 65 - }
+18 -4
config/shield.ts
··· 6 6 enabled: true, 7 7 directives: { 8 8 defaultSrc: [`'self'`], 9 - scriptSrc: [`'self'`, '@nonce'], 10 - styleSrc: [`'self'`, `'unsafe-inline'`, 'https://cdn.jsdelivr.net', 'https://fonts.bunny.net'], 11 - imgSrc: [`'self'`, 'data:', 'https://cdn.bsky.app', 'https://video.bsky.app', 'https://video.cdn.bsky.app'], 12 - connectSrc: app.inDev ? [`'self'`, '@viteUrl', 'ws://localhost:*'] : [`'self'`], 9 + scriptSrc: [`'self'`, '@nonce', 'https://ph.btao.org'], 10 + styleSrc: [ 11 + `'self'`, 12 + `'unsafe-inline'`, 13 + 'https://cdn.jsdelivr.net', 14 + 'https://fonts.bunny.net', 15 + ], 16 + imgSrc: [ 17 + `'self'`, 18 + 'data:', 19 + 'https://cdn.bsky.app', 20 + 'https://video.bsky.app', 21 + 'https://video.cdn.bsky.app', 22 + ], 23 + connectSrc: app.inDev 24 + ? [`'self'`, '@viteUrl', 'ws://localhost:*', 'https://ph.btao.org'] 25 + : [`'self'`, 'https://ph.btao.org'], 13 26 fontSrc: [`'self'`, 'https://cdn.jsdelivr.net', 'https://fonts.bunny.net'], 27 + workerSrc: [`'self'`, 'blob:', 'data:'], 14 28 objectSrc: [`'none'`], 15 29 baseUri: [`'self'`], 16 30 formAction: [`'self'`],
+1
package.json
··· 90 90 "better-sqlite3": "^12.8.0", 91 91 "edge.js": "^6.5.0", 92 92 "luxon": "^3.7.2", 93 + "posthog-js": "^1.367.0", 93 94 "posthog-node": "^5.29.2", 94 95 "reflect-metadata": "^0.2.2", 95 96 "tailwindcss": "^4.2.2"
+173
pnpm-lock.yaml
··· 62 62 luxon: 63 63 specifier: ^3.7.2 64 64 version: 3.7.2 65 + posthog-js: 66 + specifier: ^1.367.0 67 + version: 1.367.0 65 68 posthog-node: 66 69 specifier: ^5.29.2 67 70 version: 5.29.2(rxjs@7.8.2) ··· 983 986 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 984 987 engines: {node: '>= 8'} 985 988 989 + '@opentelemetry/api-logs@0.208.0': 990 + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} 991 + engines: {node: '>=8.0.0'} 992 + 986 993 '@opentelemetry/api-logs@0.213.0': 987 994 resolution: {integrity: sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==} 988 995 engines: {node: '>=8.0.0'} ··· 1010 1017 peerDependencies: 1011 1018 '@opentelemetry/api': '>=1.0.0 <1.10.0' 1012 1019 1020 + '@opentelemetry/core@2.2.0': 1021 + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} 1022 + engines: {node: ^18.19.0 || >=20.6.0} 1023 + peerDependencies: 1024 + '@opentelemetry/api': '>=1.0.0 <1.10.0' 1025 + 1013 1026 '@opentelemetry/core@2.6.0': 1014 1027 resolution: {integrity: sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==} 1015 1028 engines: {node: ^18.19.0 || >=20.6.0} ··· 1024 1037 1025 1038 '@opentelemetry/exporter-logs-otlp-grpc@0.213.0': 1026 1039 resolution: {integrity: sha512-QiRZzvayEOFnenSXi85Eorgy5WTqyNQ+E7gjl6P6r+W3IUIwAIH8A9/BgMWfP056LwmdrBL6+qvnwaIEmug6Yg==} 1040 + engines: {node: ^18.19.0 || >=20.6.0} 1041 + peerDependencies: 1042 + '@opentelemetry/api': ^1.3.0 1043 + 1044 + '@opentelemetry/exporter-logs-otlp-http@0.208.0': 1045 + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} 1027 1046 engines: {node: ^18.19.0 || >=20.6.0} 1028 1047 peerDependencies: 1029 1048 '@opentelemetry/api': ^1.3.0 ··· 1341 1360 peerDependencies: 1342 1361 '@opentelemetry/api': ^1.3.0 1343 1362 1363 + '@opentelemetry/otlp-exporter-base@0.208.0': 1364 + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} 1365 + engines: {node: ^18.19.0 || >=20.6.0} 1366 + peerDependencies: 1367 + '@opentelemetry/api': ^1.3.0 1368 + 1344 1369 '@opentelemetry/otlp-exporter-base@0.213.0': 1345 1370 resolution: {integrity: sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg==} 1346 1371 engines: {node: ^18.19.0 || >=20.6.0} ··· 1349 1374 1350 1375 '@opentelemetry/otlp-grpc-exporter-base@0.213.0': 1351 1376 resolution: {integrity: sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw==} 1377 + engines: {node: ^18.19.0 || >=20.6.0} 1378 + peerDependencies: 1379 + '@opentelemetry/api': ^1.3.0 1380 + 1381 + '@opentelemetry/otlp-transformer@0.208.0': 1382 + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} 1352 1383 engines: {node: ^18.19.0 || >=20.6.0} 1353 1384 peerDependencies: 1354 1385 '@opentelemetry/api': ^1.3.0 ··· 1405 1436 peerDependencies: 1406 1437 '@opentelemetry/api': ^1.0.0 1407 1438 1439 + '@opentelemetry/resources@2.2.0': 1440 + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} 1441 + engines: {node: ^18.19.0 || >=20.6.0} 1442 + peerDependencies: 1443 + '@opentelemetry/api': '>=1.3.0 <1.10.0' 1444 + 1408 1445 '@opentelemetry/resources@2.6.0': 1409 1446 resolution: {integrity: sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==} 1410 1447 engines: {node: ^18.19.0 || >=20.6.0} ··· 1417 1454 peerDependencies: 1418 1455 '@opentelemetry/api': '>=1.3.0 <1.10.0' 1419 1456 1457 + '@opentelemetry/sdk-logs@0.208.0': 1458 + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} 1459 + engines: {node: ^18.19.0 || >=20.6.0} 1460 + peerDependencies: 1461 + '@opentelemetry/api': '>=1.4.0 <1.10.0' 1462 + 1420 1463 '@opentelemetry/sdk-logs@0.213.0': 1421 1464 resolution: {integrity: sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g==} 1422 1465 engines: {node: ^18.19.0 || >=20.6.0} 1423 1466 peerDependencies: 1424 1467 '@opentelemetry/api': '>=1.4.0 <1.10.0' 1425 1468 1469 + '@opentelemetry/sdk-metrics@2.2.0': 1470 + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} 1471 + engines: {node: ^18.19.0 || >=20.6.0} 1472 + peerDependencies: 1473 + '@opentelemetry/api': '>=1.9.0 <1.10.0' 1474 + 1426 1475 '@opentelemetry/sdk-metrics@2.6.0': 1427 1476 resolution: {integrity: sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==} 1428 1477 engines: {node: ^18.19.0 || >=20.6.0} ··· 1437 1486 1438 1487 '@opentelemetry/sdk-node@0.213.0': 1439 1488 resolution: {integrity: sha512-8s7SQtY8DIAjraXFrUf0+I90SBAUQbsMWMtUGKmusswRHWXtKJx42aJQMoxEtC82Csqj+IlBH6FoP8XmmUDSrQ==} 1489 + engines: {node: ^18.19.0 || >=20.6.0} 1490 + peerDependencies: 1491 + '@opentelemetry/api': '>=1.3.0 <1.10.0' 1492 + 1493 + '@opentelemetry/sdk-trace-base@2.2.0': 1494 + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} 1440 1495 engines: {node: ^18.19.0 || >=20.6.0} 1441 1496 peerDependencies: 1442 1497 '@opentelemetry/api': '>=1.3.0 <1.10.0' ··· 1543 1598 '@posthog/core@1.25.2': 1544 1599 resolution: {integrity: sha512-h2FO7ut/BbfwpAXWpwdDHTzQgUo9ibDFEs6ZO+3cI3KPWQt5XwczK1OLAuPprcjm8T/jl0SH8jSFo5XdU4RbTg==} 1545 1600 1601 + '@posthog/types@1.367.0': 1602 + resolution: {integrity: sha512-FUcTEAeKhuHKyCcTQPx/sTN3s8S+PusPsiP8T/LrG/T7pDkwMfNZG0/P630JX6fT6qiW0moVvVSsaXgZDJF7wg==} 1603 + 1546 1604 '@protobufjs/aspromise@1.1.2': 1547 1605 resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} 1548 1606 ··· 2010 2068 2011 2069 '@types/tedious@4.0.14': 2012 2070 resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} 2071 + 2072 + '@types/trusted-types@2.0.7': 2073 + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} 2013 2074 2014 2075 '@types/validator@13.15.10': 2015 2076 resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} ··· 2451 2512 dlv@1.1.3: 2452 2513 resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} 2453 2514 2515 + dompurify@3.3.3: 2516 + resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} 2517 + 2454 2518 dunder-proto@1.0.1: 2455 2519 resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 2456 2520 engines: {node: '>= 0.4'} ··· 2693 2757 resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 2694 2758 engines: {node: ^12.20 || >= 14.13} 2695 2759 2760 + fflate@0.4.8: 2761 + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} 2762 + 2696 2763 figures@6.1.0: 2697 2764 resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} 2698 2765 engines: {node: '>=18'} ··· 3501 3568 resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} 3502 3569 engines: {node: '>=0.10.0'} 3503 3570 3571 + posthog-js@1.367.0: 3572 + resolution: {integrity: sha512-jWNwB8XjlVUC9PbGaIlmsyohUDMBrwf7cvLuOY3lIOmWVO3L6VxTE3GZShjxpFKQtmWcPxFbf1hcbct1YCb6xg==} 3573 + 3504 3574 posthog-node@5.29.2: 3505 3575 resolution: {integrity: sha512-rI7kkF0XqDc0G1qjx+Hb4iuY9NAlL+XQNoGOpnEpRNTUcXvjY6WlsRGZ9m2whgc39emrrYdszi/YT8wZkr2xsg==} 3506 3576 engines: {node: ^20.20.0 || >=22.22.0} ··· 3513 3583 powershell-utils@0.1.0: 3514 3584 resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} 3515 3585 engines: {node: '>=20'} 3586 + 3587 + preact@10.29.1: 3588 + resolution: {integrity: sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==} 3516 3589 3517 3590 prebuild-install@7.1.3: 3518 3591 resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} ··· 3572 3645 qs@6.15.1: 3573 3646 resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} 3574 3647 engines: {node: '>=0.6'} 3648 + 3649 + query-selector-shadow-dom@1.0.1: 3650 + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} 3575 3651 3576 3652 queue-microtask@1.2.3: 3577 3653 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} ··· 4099 4175 resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} 4100 4176 engines: {node: '>= 8'} 4101 4177 4178 + web-vitals@5.2.0: 4179 + resolution: {integrity: sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==} 4180 + 4102 4181 which@2.0.2: 4103 4182 resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 4104 4183 engines: {node: '>= 8'} ··· 5015 5094 '@nodelib/fs.scandir': 2.1.5 5016 5095 fastq: 1.20.1 5017 5096 5097 + '@opentelemetry/api-logs@0.208.0': 5098 + dependencies: 5099 + '@opentelemetry/api': 1.9.1 5100 + 5018 5101 '@opentelemetry/api-logs@0.213.0': 5019 5102 dependencies: 5020 5103 '@opentelemetry/api': 1.9.1 ··· 5087 5170 dependencies: 5088 5171 '@opentelemetry/api': 1.9.1 5089 5172 5173 + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.1)': 5174 + dependencies: 5175 + '@opentelemetry/api': 1.9.1 5176 + '@opentelemetry/semantic-conventions': 1.40.0 5177 + 5090 5178 '@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.1)': 5091 5179 dependencies: 5092 5180 '@opentelemetry/api': 1.9.1 ··· 5106 5194 '@opentelemetry/otlp-grpc-exporter-base': 0.213.0(@opentelemetry/api@1.9.1) 5107 5195 '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1) 5108 5196 '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.1) 5197 + 5198 + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.1)': 5199 + dependencies: 5200 + '@opentelemetry/api': 1.9.1 5201 + '@opentelemetry/api-logs': 0.208.0 5202 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5203 + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.1) 5204 + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) 5205 + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) 5109 5206 5110 5207 '@opentelemetry/exporter-logs-otlp-http@0.213.0(@opentelemetry/api@1.9.1)': 5111 5208 dependencies: ··· 5561 5658 transitivePeerDependencies: 5562 5659 - supports-color 5563 5660 5661 + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.1)': 5662 + dependencies: 5663 + '@opentelemetry/api': 1.9.1 5664 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5665 + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.1) 5666 + 5564 5667 '@opentelemetry/otlp-exporter-base@0.213.0(@opentelemetry/api@1.9.1)': 5565 5668 dependencies: 5566 5669 '@opentelemetry/api': 1.9.1 ··· 5574 5677 '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1) 5575 5678 '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1) 5576 5679 '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1) 5680 + 5681 + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.1)': 5682 + dependencies: 5683 + '@opentelemetry/api': 1.9.1 5684 + '@opentelemetry/api-logs': 0.208.0 5685 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5686 + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) 5687 + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) 5688 + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.1) 5689 + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.1) 5690 + protobufjs: 7.5.4 5577 5691 5578 5692 '@opentelemetry/otlp-transformer@0.213.0(@opentelemetry/api@1.9.1)': 5579 5693 dependencies: ··· 5633 5747 transitivePeerDependencies: 5634 5748 - supports-color 5635 5749 5750 + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.1)': 5751 + dependencies: 5752 + '@opentelemetry/api': 1.9.1 5753 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5754 + '@opentelemetry/semantic-conventions': 1.40.0 5755 + 5636 5756 '@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.1)': 5637 5757 dependencies: 5638 5758 '@opentelemetry/api': 1.9.1 ··· 5645 5765 '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1) 5646 5766 '@opentelemetry/semantic-conventions': 1.40.0 5647 5767 5768 + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.1)': 5769 + dependencies: 5770 + '@opentelemetry/api': 1.9.1 5771 + '@opentelemetry/api-logs': 0.208.0 5772 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5773 + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) 5774 + 5648 5775 '@opentelemetry/sdk-logs@0.213.0(@opentelemetry/api@1.9.1)': 5649 5776 dependencies: 5650 5777 '@opentelemetry/api': 1.9.1 ··· 5653 5780 '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) 5654 5781 '@opentelemetry/semantic-conventions': 1.40.0 5655 5782 5783 + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.1)': 5784 + dependencies: 5785 + '@opentelemetry/api': 1.9.1 5786 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5787 + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) 5788 + 5656 5789 '@opentelemetry/sdk-metrics@2.6.0(@opentelemetry/api@1.9.1)': 5657 5790 dependencies: 5658 5791 '@opentelemetry/api': 1.9.1 ··· 5694 5827 '@opentelemetry/semantic-conventions': 1.40.0 5695 5828 transitivePeerDependencies: 5696 5829 - supports-color 5830 + 5831 + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.1)': 5832 + dependencies: 5833 + '@opentelemetry/api': 1.9.1 5834 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.1) 5835 + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.1) 5836 + '@opentelemetry/semantic-conventions': 1.40.0 5697 5837 5698 5838 '@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.1)': 5699 5839 dependencies: ··· 5812 5952 5813 5953 '@posthog/core@1.25.2': {} 5814 5954 5955 + '@posthog/types@1.367.0': {} 5956 + 5815 5957 '@protobufjs/aspromise@1.1.2': {} 5816 5958 5817 5959 '@protobufjs/base64@1.1.2': {} ··· 6155 6297 '@types/tedious@4.0.14': 6156 6298 dependencies: 6157 6299 '@types/node': 25.5.2 6300 + 6301 + '@types/trusted-types@2.0.7': 6302 + optional: true 6158 6303 6159 6304 '@types/validator@13.15.10': {} 6160 6305 ··· 6567 6712 6568 6713 dlv@1.1.3: {} 6569 6714 6715 + dompurify@3.3.3: 6716 + optionalDependencies: 6717 + '@types/trusted-types': 2.0.7 6718 + 6570 6719 dunder-proto@1.0.1: 6571 6720 dependencies: 6572 6721 call-bind-apply-helpers: 1.0.2 ··· 6853 7002 dependencies: 6854 7003 node-domexception: 1.0.0 6855 7004 web-streams-polyfill: 3.3.3 7005 + 7006 + fflate@0.4.8: {} 6856 7007 6857 7008 figures@6.1.0: 6858 7009 dependencies: ··· 7548 7699 dependencies: 7549 7700 xtend: 4.0.2 7550 7701 7702 + posthog-js@1.367.0: 7703 + dependencies: 7704 + '@opentelemetry/api': 1.9.1 7705 + '@opentelemetry/api-logs': 0.208.0 7706 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.1) 7707 + '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1) 7708 + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.1) 7709 + '@posthog/core': 1.25.2 7710 + '@posthog/types': 1.367.0 7711 + core-js: 3.49.0 7712 + dompurify: 3.3.3 7713 + fflate: 0.4.8 7714 + preact: 10.29.1 7715 + query-selector-shadow-dom: 1.0.1 7716 + web-vitals: 5.2.0 7717 + 7551 7718 posthog-node@5.29.2(rxjs@7.8.2): 7552 7719 dependencies: 7553 7720 '@posthog/core': 1.25.2 ··· 7555 7722 rxjs: 7.8.2 7556 7723 7557 7724 powershell-utils@0.1.0: {} 7725 + 7726 + preact@10.29.1: {} 7558 7727 7559 7728 prebuild-install@7.1.3: 7560 7729 dependencies: ··· 7632 7801 qs@6.15.1: 7633 7802 dependencies: 7634 7803 side-channel: 1.1.0 7804 + 7805 + query-selector-shadow-dom@1.0.1: {} 7635 7806 7636 7807 queue-microtask@1.2.3: {} 7637 7808 ··· 8129 8300 yaml: 2.8.3 8130 8301 8131 8302 web-streams-polyfill@3.3.3: {} 8303 + 8304 + web-vitals@5.2.0: {} 8132 8305 8133 8306 which@2.0.2: 8134 8307 dependencies:
+4
providers/posthog_provider.ts
··· 1 1 import type { ApplicationService } from '@adonisjs/core/types' 2 + import edge from 'edge.js' 2 3 import { getPostHogClient, shutdownPostHog } from '#services/posthog' 3 4 4 5 export default class PostHogProvider { ··· 7 8 boot() { 8 9 // Eagerly initialise the client so it's ready when the first request arrives 9 10 getPostHogClient() 11 + 12 + // Expose the API key to Edge templates for client-side posthog-js 13 + edge.global('posthogApiKey', process.env.POSTHOG_API_KEY || '') 10 14 } 11 15 12 16 async shutdown() {
+19
resources/js/app.js
··· 1 + import posthog from 'posthog-js' 1 2 import Alpine from '@alpinejs/csp' 2 3 import { createBackfillProgress } from './backfill_progress.ts' 3 4 import { createSearchAhead } from './search_ahead.ts' 5 + 6 + // PostHog client-side analytics 7 + var phKey = document.querySelector('meta[name="posthog-api-key"]') 8 + if (phKey) { 9 + posthog.init(phKey.getAttribute('content'), { 10 + api_host: 'https://ph.btao.org', 11 + capture_pageview: true, 12 + capture_pageleave: true, 13 + }) 14 + 15 + var phDistinctId = document.querySelector('meta[name="posthog-distinct-id"]') 16 + if (phDistinctId) { 17 + var phHandle = document.querySelector('meta[name="posthog-handle"]') 18 + posthog.identify(phDistinctId.getAttribute('content'), { 19 + handle: phHandle ? phHandle.getAttribute('content') : undefined, 20 + }) 21 + } 22 + } 4 23 5 24 Alpine.data('darkMode', function () { 6 25 return {
+7
resources/views/components/layout.edge
··· 13 13 <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/fill/style.css" /> 14 14 <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.2/src/bold/style.css" /> 15 15 <meta name="csrf-token" content="{{ csrfToken }}"> 16 + @if(posthogApiKey) 17 + <meta name="posthog-api-key" content="{{ posthogApiKey }}"> 18 + @if(auth.isAuthenticated) 19 + <meta name="posthog-distinct-id" content="{{ auth.user.did }}"> 20 + <meta name="posthog-handle" content="{{ auth.user.handle }}"> 21 + @endif 22 + @endif 16 23 @if($slots.head) 17 24 {{{ await $slots.head() }}} 18 25 @endif
-1
start/kernel.ts
··· 39 39 () => import('@adonisjs/session/session_middleware'), 40 40 () => import('@adonisjs/shield/shield_middleware'), 41 41 () => import('@adonisjs/auth/initialize_auth_middleware'), 42 - () => import('#middleware/posthog_middleware'), 43 42 ]) 44 43 45 44 /**