From f06a3e864ee9f5daa913f0e414352c3a3e855dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Mon, 18 Dec 2023 12:33:00 +0100 Subject: [PATCH] add analytics to the gateway --- package.json | 5 +- src/app.ts | 10 +++- src/utils/analytics.ts | 120 +++++++++++++++++++++++++++++++++++++++++ src/worker.ts | 30 +++++++++-- tsconfig.json | 1 + yarn.lock | 105 +++++++++++++++++++++++++++++++----- 6 files changed, 250 insertions(+), 21 deletions(-) create mode 100644 src/utils/analytics.ts diff --git a/package.json b/package.json index 6116672..bcf6ac6 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,12 @@ } ], "devDependencies": { - "@cloudflare/workers-types": "^4.20230214.0", + "@cloudflare/workers-types": "^4.20231121.0", "@ensdomains/ens-contracts": "^0.0.21", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", "@types/dns-packet": "^5.2.4", + "@types/express": "^4.17.21", "@types/supertest": "^2.0.11", "bundlesize2": "^0.0.31", "chai": "^4.3.7", @@ -73,7 +74,7 @@ }, "dependencies": { "@chainlink/ccip-read-server": "^0.2.1", - "@ensdomains/ccip-read-cf-worker": "^0.0.1", + "@ensdomains/ccip-read-cf-worker": "^0.0.3", "@ensdomains/dnsprovejs": "^0.4.1", "dotenv": "^16.0.3", "ethers": "^5.7.2", diff --git a/src/app.ts b/src/app.ts index b66ee18..0693101 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,7 +6,8 @@ import * as qTypes from 'dns-packet/types'; export function makeApp( sendQuery: ConstructorParameters[0], path: string, - Server: any + Server: any, + trackEvent?: Function ) { const prover = new DNSProver(sendQuery); @@ -22,6 +23,13 @@ export function makeApp( const decodedName = packet.name.decode( Buffer.from(name.slice(2), 'hex') ); + + if (trackEvent) { + trackEvent('resolve', { + props: { name: decodedName, qtype: qTypes.toString(qtype) }, + }, true); + } + const result = await prover.queryWithProof( qTypes.toString(qtype), decodedName diff --git a/src/utils/analytics.ts b/src/utils/analytics.ts new file mode 100644 index 0000000..d16886b --- /dev/null +++ b/src/utils/analytics.ts @@ -0,0 +1,120 @@ +import { Request as CFWRequest } from '@cloudflare/workers-types'; +import { Request as ExpressRequest } from 'express'; + +interface TrackerOptions { + apiEndpoint?: string; + enableLogging?: boolean; +} + +interface TrackingOptions { + props?: any; + data?: any; +} + +interface RequestBody { + domain: string; + name: string; + url: string; + referrer?: string; + props?: any; + data?: any; +} + +export class Tracker { + domain = ''; + enableLogging = false; + apiEndpoint = 'https://plausible.io/api/event'; + + constructor(domain: string, options: TrackerOptions = {}) { + this.domain = domain; + this.apiEndpoint = options.apiEndpoint || this.apiEndpoint; + this.enableLogging = options.enableLogging || this.enableLogging; + } + + log(message: string) { + if (this.enableLogging) { + console.log(message); + } + } + + async trackPageview( + req: CFWRequest | ExpressRequest, + options?: TrackingOptions, + includeUserDetails?: boolean + ) { + await this.trackEvent(req, 'pageview', options, includeUserDetails); + } + + async trackEvent( + req: CFWRequest | ExpressRequest, + name: string, + { props, data }: TrackingOptions = {}, + includeUserDetails = false + ) { + try { + if (!name || typeof name !== 'string') { + throw new Error('Invalid event name'); + } + + const body: RequestBody = { + domain: this.domain, + name: name, + url: + 'originalUrl' in req // means it's express request + ? `${req.protocol}://${req.get('host')}${req.originalUrl}` + : req.url, + }; + + const requestInfo = 'originalUrl' in req ? req : req.headers; + + if ( + requestInfo.get('Referrer') + ) { + body.referrer = requestInfo.get('Referrer') || + ''; + } + + if (props) { + body.props = props; + } + + if (data) { + body.data = data; + } + + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + const userAgent = requestInfo.get('User-Agent'); + if (userAgent) { + headers['User-Agent'] = userAgent; + headers['X-Forwarded-For'] = '127.0.0.1'; + } + + if (includeUserDetails) { + headers['X-Forwarded-For'] = + (requestInfo as ExpressRequest)?.socket?.remoteAddress || + requestInfo.get('CF-Connecting-IP') || + ''; + } + + this.log(JSON.stringify(headers)); + this.log(JSON.stringify(body)); + + const response = await fetch(this.apiEndpoint, { + method: 'POST', + headers: headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`Plausible API responded with ${response.status}`); + } + + this.log(`Event tracked: ${name}`); + } catch (err) { + console.error('Plausible error:', err); + } + } +} diff --git a/src/worker.ts b/src/worker.ts index 9464e81..c2e9897 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,17 +1,39 @@ +import { + Request as CFWRequest, + ExecutionContext, +} from '@cloudflare/workers-types'; import { Server } from '@ensdomains/ccip-read-cf-worker'; import { dohQuery } from '@ensdomains/dnsprovejs'; import { makeApp } from './app'; +import { Tracker } from './utils/analytics'; -const routeHandler = (env: any) => { +interface ENV { + DOH_GATEWAY_URL: string; +} + +const tracker = new Tracker('ccip-read-dns-worker.ens-cf.workers.dev', { enableLogging: true }); + +const routeHandler = (env: ENV, trackEvent?: Function) => { const { DOH_GATEWAY_URL } = env; - const app = makeApp(dohQuery(DOH_GATEWAY_URL as string), '/', Server); + const app = makeApp( + dohQuery(DOH_GATEWAY_URL as string), + '/', + Server, + trackEvent + ); console.log(`Serving with DoH Resolver ${DOH_GATEWAY_URL}`); return app; }; module.exports = { - fetch: async function(request: Request, env: any, _context: any) { - const router = routeHandler(env); + fetch: async function( + request: CFWRequest, + env: ENV, + _context: ExecutionContext + ) { + await tracker.trackEvent(request, 'request', {}, true); + await tracker.trackPageview(request, {}, true); + const router = routeHandler(env, tracker.trackEvent.bind(tracker, request)); return await router.handle(request); }, }; diff --git a/tsconfig.json b/tsconfig.json index 72358a5..8f3a080 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "compilerOptions": { "module": "esnext", "lib": ["dom", "esnext"], + "types": ["@cloudflare/workers-types"], "importHelpers": true, // output .d.ts declaration files for consumers "declaration": true, diff --git a/yarn.lock b/yarn.lock index c32ffe8..82f694b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -951,10 +951,10 @@ ethers "^5.3.1" express "^4.17.1" -"@cloudflare/workers-types@^4.20230214.0": - version "4.20230214.0" - resolved "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230214.0.tgz" - integrity sha512-PfpzIKjMA1P0HLtzZVKp/V4/rAiSkgIVwGKbQleTmj7y5O62NbWP9gAxzOeQ4LpUc8UL17KRF2rXTgNKP2HNFw== +"@cloudflare/workers-types@^4.20231121.0": + version "4.20231121.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20231121.0.tgz#6a1c0bfc93aca4643d43f6439cfb7aca8058f289" + integrity sha512-+kWfpCkqiepwAKXyHoE0gnkPgkLhz0/9HOBIGhHRsUvUKvhUtm3mbqqoGRWgF1qcjzrDUBbrrOq4MYHfFtc2RA== "@cnakazawa/watch@^1.0.3": version "1.0.4" @@ -969,13 +969,13 @@ resolved "https://registry.npmjs.org/@ensdomains/buffer/-/buffer-0.1.1.tgz" integrity sha512-92SfSiNS8XorgU7OUBHo/i1ZU7JV7iz/6bKuLPNVsMxV79/eI7fJR6jfJJc40zAHjs3ha+Xo965Idomlq3rqnw== -"@ensdomains/ccip-read-cf-worker@^0.0.1": - version "0.0.1" - resolved "https://registry.npmjs.org/@ensdomains/ccip-read-cf-worker/-/ccip-read-cf-worker-0.0.1.tgz" - integrity sha512-g87a2nwL9dJp+DCcQRA37VzEcmuRuIdy0fZWTppOHzB6XvlSlt7AuqexIdPObHZZzNpV1pePBySFyOnVe0Lt5Q== +"@ensdomains/ccip-read-cf-worker@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@ensdomains/ccip-read-cf-worker/-/ccip-read-cf-worker-0.0.3.tgz#e5b460d034e20d4d67a4ff6ee37949945d3d5de3" + integrity sha512-QlCzuLJ8W1U2y0SsdnxrdKow8p37hkDAuCbIPujPNyyAKoQsFxhOGRCyI91mM/2m5xGWT/iSekFK280dqhvtbQ== dependencies: - ethers "^5.3.1" - itty-router "^2.6.1" + ethers "^5.7.2" + itty-router "^4.0.3" "@ensdomains/dnsprovejs@^0.4.1": version "0.4.1" @@ -1856,6 +1856,14 @@ dependencies: "@types/node" "*" +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/chai-as-promised@^7.1.5": version "7.1.5" resolved "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz" @@ -1868,6 +1876,13 @@ resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.4.tgz" integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw== +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + "@types/cookiejar@*": version "2.1.2" resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz" @@ -1890,6 +1905,26 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/express-serve-static-core@^4.17.33": + version "4.17.41" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz#5077defa630c2e8d28aa9ffc2c01c157c305bef6" + integrity sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.2": version "4.1.6" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz" @@ -1897,6 +1932,11 @@ dependencies: "@types/node" "*" +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" @@ -1940,6 +1980,16 @@ resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz" integrity sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw== +"@types/mime@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" + integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + "@types/node@*": version "18.13.0" resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz" @@ -1967,6 +2017,16 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz" integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== +"@types/qs@*": + version "6.9.10" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.10.tgz#0af26845b5067e1c9a622658a51f60a3934d51e8" + integrity sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz" @@ -1986,6 +2046,23 @@ resolved "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.1.tgz" integrity sha512-giB9gzDeiCeloIXDgzFBCgjj1k4WxcDrZtGl6h1IqmUPlxF+Nx8Ve+96QCyDZ/HseB/uvDsKbpib9hU5cU53pw== +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.5" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" + integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + dependencies: + "@types/http-errors" "*" + "@types/mime" "*" + "@types/node" "*" + "@types/stack-trace@0.0.29": version "0.0.29" resolved "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz" @@ -5298,10 +5375,10 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -itty-router@^2.6.1: - version "2.6.6" - resolved "https://registry.npmjs.org/itty-router/-/itty-router-2.6.6.tgz" - integrity sha512-hIPHtXGymCX7Lzb2I4G6JgZFE4QEEQwst9GORK7sMYUpJvLfy4yZJr95r04e8DzoAnj6HcxM2m4TbK+juu+18g== +itty-router@^4.0.3: + version "4.0.23" + resolved "https://registry.yarnpkg.com/itty-router/-/itty-router-4.0.23.tgz#44bb79134567773d3356e9972913e8fd6ed8a7a0" + integrity sha512-tP1NI8PVK43vWlBnIPqj47ni5FDSczFviA4wgBznscndo8lEvBA+pO3DD1rNbIQPcZhprr775iUTunyGvQMcBw== jest-changed-files@^25.5.0: version "25.5.0"