-
-
Save ranic/80459104def4e4bcd73d5c77b817ee43 to your computer and use it in GitHub Desktop.
<html> | |
<head> | |
<title>Mixpanel Tracking Proxy Demo</title> | |
<script type="text/javascript"> | |
/** | |
* Configuration Variables - CHANGE THESE! | |
*/ | |
const MIXPANEL_PROJECT_TOKEN = YOUR_MIXPANEL_PROJECT_TOKEN; // e.g. "67e8bfdec29d84ab2d36ae18c57b8535" | |
const MIXPANEL_PROXY_DOMAIN = YOUR_PROXY_DOMAIN; // e.g. "https://proxy-eoca2pin3q-uc.a.run.app" | |
/** | |
* Set the MIXPANEL_CUSTOM_LIB_URL - No need to change this | |
*/ | |
const MIXPANEL_CUSTOM_LIB_URL = MIXPANEL_PROXY_DOMAIN + "/lib.min.js"; | |
/** | |
* Load the Mixpanel JS library asyncronously via the js snippet | |
*/ | |
(function(f,b){if(!b.__SV){var e,g,i,h;window.mixpanel=b;b._i=[];b.init=function(e,f,c){function g(a,d){var b=d.split(".");2==b.length&&(a=a[b[0]],d=b[1]);a[d]=function(){a.push([d].concat(Array.prototype.slice.call(arguments,0)))}}var a=b;"undefined"!==typeof c?a=b[c]=[]:c="mixpanel";a.people=a.people||[];a.toString=function(a){var d="mixpanel";"mixpanel"!==c&&(d+="."+c);a||(d+=" (stub)");return d};a.people.toString=function(){return a.toString(1)+".people (stub)"};i="disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking start_batch_senders people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove".split(" "); | |
for(h=0;h<i.length;h++)g(a,i[h]);var j="set set_once union unset remove delete".split(" ");a.get_group=function(){function b(c){d[c]=function(){call2_args=arguments;call2=[c].concat(Array.prototype.slice.call(call2_args,0));a.push([e,call2])}}for(var d={},e=["get_group"].concat(Array.prototype.slice.call(arguments,0)),c=0;c<j.length;c++)b(j[c]);return d};b._i.push([e,f,c])};b.__SV=1.2;e=f.createElement("script");e.type="text/javascript";e.async=!0;e.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL? | |
MIXPANEL_CUSTOM_LIB_URL:"file:"===f.location.protocol&&"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js";g=f.getElementsByTagName("script")[0];g.parentNode.insertBefore(e,g)}})(document,window.mixpanel||[]); | |
/** | |
* Initialize a Mixpanel instance using your project token and proxy domain | |
*/ | |
mixpanel.init(MIXPANEL_PROJECT_TOKEN, {debug: true, api_host: MIXPANEL_PROXY_DOMAIN}); | |
/** | |
* Track an event when the page is loaded | |
*/ | |
mixpanel.track("[Proxy Demo] Page loaded"); | |
</script> | |
</head> | |
<body> | |
<button onclick="mixpanel.track('[Proxy Demo] Button clicked')">Track event</button> | |
</body> | |
</html> |
You can't change the endpoint name itself via our SDKs, but you can host your own server that does the proxying.
For example, you could have an endpoint hosted at https://yourdomain.com/e. The handler for that endpoint could simply pass through to Mixpanel's /import API. Then every time you want to fire an event from your client, you make a request to that server.
re: IP, yes if you pass ip
explicitly it will be geoencoded on our servers. This is documented here: https://docs.mixpanel.com/docs/tracking/how-tos/effective-server#tracking-geolocation
Thanks for providing this example. It seems that when I start proxying requests through an nginx instance I'm losing the geolocation ability. I'm definitely passing along the client's IP in the headers (X-Real-IP). Since it's just JS -> nginx -> mixpanel, there's no chance to modify the payload to add an IP property. @ranic can you tell me where mixpanel is expecting to look to pick up the client IP?
We look at the ip
event property. Our SDK sets it automatically if you don't set it yourself, but in this case, it might be easier to set it yourself (include a property called ip
on all your events which is set to the client's IP address). You can do this in your client-side tracking code.
See the code example here
Hmm, I checked the payload when going straight client -> MixPanel and it didn't include an ip property, but MixPanel still calculated geolocation. This makes sense since the front-end code doesn't have a way to know the IP address without doing a server call.
Now I'm doing client -> nginx proxy -> MixPanel. I don't have a way to set ip
in the payload (well, I'd have to first call some service to get the IP on the client). In nginx I have the IP, but I don't believe I can easily modify the json payload with an nginx rule.
It's interesting that it worked client -> MixPanel directly, even without an ip
property set. I wonder what it uses to determine IP in that setup – and if I can replicate it with the proxy.
Hello, I did add the ip field and worked perfectly with the server sdk.
Well I had to rebuild the payload and send it in the batch import on the server side using the mixpanel server sdk
Browser -> Server->Append the ip address of the client -> Mixpanel (Node sdk)
@StephenHaney
This is how i extracted the remote client behind the proxy
const forwarded = req.headers['x-forwarded-for'] const ip = forwarded ? forwarded.split(/\s*,\s*/)[0] : req.socket.remoteAddress;
as the proxies start to pile up the ip addresses of the remote addresses, you will end up having a http header (X-Forwarded-For) with a string that consists of all the ip addresses that the request went through, your first ip address is the client's one.
Thanks for sharing @mgara! This makes sense if you're using a server. I might end up using this approach too.
In the pure client -> nginx proxy -> MixPanel approach, there is no chance to add an ip
field. And if you use a server in the middle, there's not much point in using the nginx proxy at all. The server can just call directly to MixPanel's API.
On the header approach: just to test, I used $remote_addr
(which is only the client IP) for both X-Real-IP
and X-Forwarded-For
headers. I looked at the nginx logs to make sure it sent the correct client IP for both of those headers. But MixPanel still didn't calculate geolocation for those requests.
For future googlers, something before my proxy was changing $remote_addr
to be one hop away client's actual IPs. Often only a few digits different, so hard to notice. For some reason, geolocation wasn't working with these hop IPs.
Instead of using $remote_addr
, I switched to using the first IP from $http_x_forwarded_for
as my X-Real-IP
header that I send to MixPanel. After this, geolocation started working.
Here's the nginx snippet to grab the first value from $http_x_forwarded_for
map $http_x_forwarded_for $original_client_ip {
# captures everything upto the first comma
"~^([^,]+)" $1;
default "";
}
and then later:
proxy_set_header X-Real-IP $original_client_ip;
If we use track users from a proxy (e.g. "exampleproxy.com), are we able to track users across two separate domains (e.g. "example1.com" , "example2.com") if we are using the same proxy server on both domains?
I'm getting 404 for https://MY_PROXY_DOMAIN/lib.min.js, which also happens if i try directly with https://api.mixpanel.js/lib.min.js.
Am i missing some step?
Hello, thanks for this example, however is there a way to change the endpoint from
/track
to something else to prevent heuristic algorithms from blocking tracking events ?
@mgara as of 2.48.0, you can configure the endpoint /track
to something different. More info here: https://github.com/mixpanel/mixpanel-js/releases/tag/v2.48.0
hello , I used the exact given nginx.conf file, I only changed to a specific port because I am using localhost for running my app also on some other port.
but still got this error.
mixpanel.service.ts:14 Mixpanel error: Bad HTTP status: 0
mixpanel.service.ts:14
POST https://localhost:4999/lib.min.js/track/?verbose=1&ip=1&_=1706010860416 net::ERR_SSL_PROTOCOL_ERROR
here is my nginx.conf file code:
events {}
http {
server {
listen 4999 default backlog=16384;
listen [::]:4999 default backlog=16384;
location /lib.min.js {
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js;
}
location /lib.js {
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://cdn.mxpnl.com/libs/mixpanel-2-latest.js;
}
location /decide {
proxy_set_header Host decide.mixpanel.com;
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://decide.mixpanel.com/decide;
}
location / {
proxy_set_header Host api.mixpanel.com;
proxy_set_header X-Real-IP $http_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass https://api.mixpanel.com/;
}
}
}
and thats my Angular project code
myid = "ae02abe7863092fe2xxxxxxxxxxxx"
MIXPANEL_CUSTOM_LIB_URL = "https://localhost:4999/lib.min.js";
constructor() {}
init() {
mixpanel.init(this.myid, {
api_host: this.MIXPANEL_CUSTOM_LIB_URL,
debug: true,
track_pageview: true,
});
}
Does anyone have a working example of express/nodejs
proxy setup?
No matter what I try, geocoding doesn't go through even though X-Real-IP
is set (tried first value before comma of x_forwarded_for
as suggested by @StephenHaney above and tried other variations too) plus other headers as per example are set. The proxy logs show all values are set and sent to mixpanel but still no luck
@arthabus Does anyone have a working example of
express/nodejs
proxy setup?No matter what I try, geocoding doesn't go through even though
X-Real-IP
is set (tried first value before comma ofx_forwarded_for
as suggested by @StephenHaney above and tried other variations too) plus other headers as per example are set. The proxy logs show all values are set and sent to mixpanel but still no luck
The proxy we set up is in Deno, but you can probably work your way around it with express/nodejs
as it's fairly similar:
import { serve } from "https://deno.land/std@0.192.0/http/server.ts";
const port = Number(Deno.env.get("PORT") ?? 3009);
serve(
async (request: Request, conn: any) => {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/instrumentation")) {
return await proxyMixpanelCall(request, conn);
}
// ...
},
{ port },
);
async function proxyMixpanelCall(request: Request, conn: any) {
const headers = new Headers(request.headers);
const url = new URL(request.url.replace("/instrumentation", "/track"));
url.port = "";
url.protocol = "https:";
url.host = "api.mixpanel.com";
const remoteAddress = getRemoteAddress(request, conn);
const hostname = getHostname(request, url);
headers.set("Host", "api.mixpanel.com");
headers.set("X-Real-IP", remoteAddress);
headers.set("X-Forwarded-For", remoteAddress);
headers.set("X-Forwarded-Host", hostname);
const proxyRequest = new Request(url.toString(), {
method: request.method,
headers,
body: request.body,
});
try {
return await fetch(proxyRequest);
} catch (e) {
console.log("Failed to proxy call to Mixpanel", e);
return null;
}
}
function getRemoteAddress(request: Request, conn: any) {
return request.headers.get("x-forwarded-for") || conn.remoteAddr?.hostname;
}
function getHostname(request: Request, url: URL) {
return request.headers.get("x-forwarded-host") || url.hostname;
}
@ianlet thanks a lot mate! While I was adopting your solution I realised that I didn't proxy the query params so that was the missing bit. After setting the full mixpanel urls properly with query params it all started to work. Maybe this would help someone in the future.
Can I just add the ip address to the payload and the geolocation will be made on your side ? (maxpanel)