|
| 1 | +// with thanks to https://github.com/codeniko/simple-tracker/blob/master/examples/server-examples/aws-lambda/google-analytics.js |
| 2 | +const request = require("request"); |
| 3 | +const querystring = require("querystring"); |
| 4 | +const uuidv4 = require("uuid/v4"); |
| 5 | + |
| 6 | +const GA_ENDPOINT = `https://www.google-analytics.com/collect`; |
| 7 | + |
| 8 | +// Domains to whitelist. Replace with your own! |
| 9 | +const originWhitelist = []; // keep this empty and append domains to whitelist using whiteListDomain() |
| 10 | +whitelistDomain("test.com"); |
| 11 | +whitelistDomain("nfeld.com"); |
| 12 | + |
| 13 | +function whitelistDomain(domain, addWww = true) { |
| 14 | + const prefixes = ["https://", "http://"]; |
| 15 | + if (addWww) { |
| 16 | + prefixes.push("https://www."); |
| 17 | + prefixes.push("http://www."); |
| 18 | + } |
| 19 | + prefixes.forEach(prefix => originWhitelist.push(prefix + domain)); |
| 20 | +} |
| 21 | + |
| 22 | +function proxyToGoogleAnalytics(event, done) { |
| 23 | + // get GA params whether GET or POST request |
| 24 | + const params = |
| 25 | + event.httpMethod.toUpperCase() === "GET" |
| 26 | + ? event.queryStringParameters |
| 27 | + : JSON.parse(event.body); |
| 28 | + const headers = event.headers || {}; |
| 29 | + |
| 30 | + // attach other GA params, required for IP address since client doesn't have access to it. UA and CID can be sent from client |
| 31 | + params.uip = headers["x-forwarded-for"] || headers["x-bb-ip"] || ""; // ip override. Look into headers for clients IP address, as opposed to IP address of host running lambda function |
| 32 | + params.ua = params.ua || headers["user-agent"] || ""; // user agent override |
| 33 | + params.cid = params.cid || uuidv4(); // REQUIRED: use given cid, or generate a new one as last resort. Generating should be avoided because one user can show up in GA multiple times. If user refresh page `n` times, you'll get `n` pageviews logged into GA from "different" users. Client should generate a uuid and store in cookies, local storage, or generate a fingerprint. Check simple-tracker client example |
| 34 | + |
| 35 | + console.info("proxying params:", params); |
| 36 | + const qs = querystring.stringify(params); |
| 37 | + |
| 38 | + const reqOptions = { |
| 39 | + method: "POST", |
| 40 | + headers: { |
| 41 | + "Content-Type": "image/gif" |
| 42 | + }, |
| 43 | + url: GA_ENDPOINT, |
| 44 | + body: qs |
| 45 | + }; |
| 46 | + |
| 47 | + request(reqOptions, (error, result) => { |
| 48 | + if (error) { |
| 49 | + console.info("googleanalytics error!", error); |
| 50 | + } else { |
| 51 | + console.info( |
| 52 | + "googleanalytics status code", |
| 53 | + result.statusCode, |
| 54 | + result.statusMessage |
| 55 | + ); |
| 56 | + } |
| 57 | + }); |
| 58 | + |
| 59 | + done(); |
| 60 | +} |
| 61 | + |
| 62 | +exports.handler = function(event, context, callback) { |
| 63 | + const origin = event.headers["origin"] || event.headers["Origin"] || ""; |
| 64 | + console.log(`Received ${event.httpMethod} request from, origin: ${origin}`); |
| 65 | + |
| 66 | + const isOriginWhitelisted = originWhitelist.indexOf(origin) >= 0; |
| 67 | + console.info("is whitelisted?", isOriginWhitelisted); |
| 68 | + |
| 69 | + const headers = { |
| 70 | + //'Access-Control-Allow-Origin': '*', // allow all domains to POST. Use for localhost development only |
| 71 | + "Access-Control-Allow-Origin": isOriginWhitelisted |
| 72 | + ? origin |
| 73 | + : originWhitelist[0], |
| 74 | + "Access-Control-Allow-Methods": "GET,POST,OPTIONS", |
| 75 | + "Access-Control-Allow-Headers": "Content-Type,Accept" |
| 76 | + }; |
| 77 | + |
| 78 | + const done = () => { |
| 79 | + callback(null, { |
| 80 | + statusCode: 200, |
| 81 | + headers, |
| 82 | + body: "" |
| 83 | + }); |
| 84 | + }; |
| 85 | + |
| 86 | + const httpMethod = event.httpMethod.toUpperCase(); |
| 87 | + |
| 88 | + if (event.httpMethod === "OPTIONS") { |
| 89 | + // CORS (required if you use a different subdomain to host this function, or a different domain entirely) |
| 90 | + done(); |
| 91 | + } else if ( |
| 92 | + (httpMethod === "GET" || httpMethod === "POST") && |
| 93 | + isOriginWhitelisted |
| 94 | + ) { |
| 95 | + // allow GET or POST, but only for whitelisted domains |
| 96 | + proxyToGoogleAnalytics(event, done); |
| 97 | + } else { |
| 98 | + callback("Not found"); |
| 99 | + } |
| 100 | +}; |
| 101 | + |
| 102 | +/* |
| 103 | + Docs on GA endpoint and example params |
| 104 | +
|
| 105 | + https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide |
| 106 | +
|
| 107 | +v: 1 |
| 108 | +_v: j67 |
| 109 | +a: 751874410 |
| 110 | +t: pageview |
| 111 | +_s: 1 |
| 112 | +dl: https://nfeld.com/contact.html |
| 113 | +dr: https://google.com |
| 114 | +ul: en-us |
| 115 | +de: UTF-8 |
| 116 | +dt: Nikolay Feldman - Software Engineer |
| 117 | +sd: 24-bit |
| 118 | +sr: 1440x900 |
| 119 | +vp: 945x777 |
| 120 | +je: 0 |
| 121 | +_u: blabla~ |
| 122 | +jid: |
| 123 | +gjid: |
| 124 | +cid: 1837873423.1522911810 |
| 125 | +tid: UA-116530991-1 |
| 126 | +_gid: 1828045325.1524815793 |
| 127 | +gtm: u4d |
| 128 | +z: 1379041260 |
| 129 | +*/ |
0 commit comments