@activescott/analytics 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/post-hog-identifier.d.ts +12 -0
- package/dist/post-hog-identifier.d.ts.map +1 -0
- package/dist/post-hog-identifier.js +23 -0
- package/dist/post-hog-identifier.js.map +1 -0
- package/dist/post-hog-pageview-tracker.d.ts +7 -0
- package/dist/post-hog-pageview-tracker.d.ts.map +1 -0
- package/dist/post-hog-pageview-tracker.js +17 -0
- package/dist/post-hog-pageview-tracker.js.map +1 -0
- package/dist/post-hog-provider.d.ts +18 -0
- package/dist/post-hog-provider.d.ts.map +1 -0
- package/dist/post-hog-provider.js +19 -0
- package/dist/post-hog-provider.js.map +1 -0
- package/dist/proxy.d.ts +76 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +215 -0
- package/dist/proxy.js.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @activescott/analytics
|
|
2
|
+
|
|
3
|
+
PostHog analytics integration for React Router apps — SPA pageview tracking, user identification, and a reverse proxy helper to avoid ad blockers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @activescott/analytics posthog-js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`posthog-js` is a peer dependency so consumers control its version. When
|
|
12
|
+
bumping the peer floor, check the
|
|
13
|
+
[posthog-js changelog](https://github.com/PostHog/posthog-js/releases) for
|
|
14
|
+
breaking changes to `PostHogConfig` or the `posthog-js/react` exports, then
|
|
15
|
+
release a matching minor of this package.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Add the provider to your app
|
|
20
|
+
|
|
21
|
+
Mount it where loader data is available (in React Router v7, that means `App`,
|
|
22
|
+
not `Layout` — `Layout` renders with undefined loader data on error
|
|
23
|
+
boundaries, and error pages intentionally get no pageview):
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// app/root.tsx
|
|
27
|
+
import { PostHogProvider } from "@activescott/analytics"
|
|
28
|
+
|
|
29
|
+
export function App() {
|
|
30
|
+
const { posthogKey, user } = useLoaderData<typeof loader>()
|
|
31
|
+
// Dynamic-import the provider module only when a key is configured so
|
|
32
|
+
// disabled/self-host installs pay zero bundle cost for posthog-js.
|
|
33
|
+
...
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Identify by stable id only — never pass emails, phone numbers, or other PII in
|
|
38
|
+
`properties` unless your privacy policy explicitly covers it:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<PostHogProvider
|
|
42
|
+
apiKey={posthogKey}
|
|
43
|
+
options={{ api_host: "/ph", ui_host: "https://us.posthog.com" }}
|
|
44
|
+
user={user ? { distinctId: user.id } : undefined}
|
|
45
|
+
>
|
|
46
|
+
...
|
|
47
|
+
</PostHogProvider>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With no `apiKey` the provider renders children with zero PostHog code paths.
|
|
51
|
+
|
|
52
|
+
### 2. Add the reverse proxy route
|
|
53
|
+
|
|
54
|
+
Create `app/routes/ph.$.ts`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createPostHogProxy } from "@activescott/analytics/proxy"
|
|
58
|
+
|
|
59
|
+
export const { loader, action } = createPostHogProxy({
|
|
60
|
+
apiHost: "https://us.i.posthog.com",
|
|
61
|
+
assetsHost: "https://us-assets.i.posthog.com",
|
|
62
|
+
// Opt in only if your privacy policy covers it: forwards the ingress-set
|
|
63
|
+
// X-Forwarded-For chain so PostHog sees client IPs for geoIP.
|
|
64
|
+
forwardIp: true,
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Register the route in `app/routes.ts`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
route("ph/*", "routes/ph.$.ts"),
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The proxy hardens the relay for you: upstream origins are fixed at
|
|
75
|
+
configuration time, `Cookie`/`Authorization` (and everything but content-type +
|
|
76
|
+
user-agent) are stripped, `Set-Cookie` is stripped from responses, request
|
|
77
|
+
bodies are capped (default 1 MiB), the upstream call has a timeout (default
|
|
78
|
+
10s), and only known PostHog path prefixes (`e`, `batch`, `decide`, `flags`,
|
|
79
|
+
`s`, `surveys`, `array`, `static`) are proxied. IP forwarding is deliberately
|
|
80
|
+
absent — `X-Forwarded-For` passthrough vs `?ip=` is a privacy decision for you
|
|
81
|
+
to make explicitly.
|
|
82
|
+
|
|
83
|
+
That's it! PostHog will now:
|
|
84
|
+
|
|
85
|
+
- Track pageviews on every client-side navigation
|
|
86
|
+
- Identify logged-in users by id
|
|
87
|
+
- Receive events through your own domain (`/ph/*`)
|
|
88
|
+
|
|
89
|
+
## Versioning
|
|
90
|
+
|
|
91
|
+
Independent plain-npm versions with git tags of the form `analytics@<version>`
|
|
92
|
+
(see repo README). Consumers pin the versioned package normally — lockfile,
|
|
93
|
+
Dependabot, and Docker all work unchanged.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface PostHogIdentifierProps {
|
|
2
|
+
distinctId?: string | null;
|
|
3
|
+
properties?: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Identifies the current user to PostHog for analytics tracking.
|
|
7
|
+
* When `distinctId` is provided, calls `posthog.identify()`.
|
|
8
|
+
* When transitioning from identified to anonymous (distinctId removed), calls `posthog.reset()`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function PostHogIdentifier({ distinctId, properties, }: PostHogIdentifierProps): null;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=post-hog-identifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-identifier.d.ts","sourceRoot":"","sources":["../src/post-hog-identifier.tsx"],"names":[],"mappings":"AAGA,UAAU,sBAAsB;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,UAAU,GACX,EAAE,sBAAsB,QAiBxB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { usePostHog } from "posthog-js/react";
|
|
3
|
+
/**
|
|
4
|
+
* Identifies the current user to PostHog for analytics tracking.
|
|
5
|
+
* When `distinctId` is provided, calls `posthog.identify()`.
|
|
6
|
+
* When transitioning from identified to anonymous (distinctId removed), calls `posthog.reset()`.
|
|
7
|
+
*/
|
|
8
|
+
export function PostHogIdentifier({ distinctId, properties, }) {
|
|
9
|
+
const posthog = usePostHog();
|
|
10
|
+
const previousDistinctIdReference = useRef(distinctId);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const previousDistinctId = previousDistinctIdReference.current;
|
|
13
|
+
if (distinctId) {
|
|
14
|
+
posthog.identify(distinctId, properties);
|
|
15
|
+
}
|
|
16
|
+
else if (previousDistinctId && !distinctId) {
|
|
17
|
+
posthog.reset();
|
|
18
|
+
}
|
|
19
|
+
previousDistinctIdReference.current = distinctId;
|
|
20
|
+
}, [distinctId, properties, posthog]);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=post-hog-identifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-identifier.js","sourceRoot":"","sources":["../src/post-hog-identifier.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAO7C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,UAAU,EACV,UAAU,GACa;IACvB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAC5B,MAAM,2BAA2B,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;IAEtD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,kBAAkB,GAAG,2BAA2B,CAAC,OAAO,CAAA;QAE9D,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;QAC1C,CAAC;aAAM,IAAI,kBAAkB,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7C,OAAO,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;QAED,2BAA2B,CAAC,OAAO,GAAG,UAAU,CAAA;IAClD,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAA;IAErC,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Captures `$pageview` events on client-side route changes.
|
|
3
|
+
* Required because `capture_pageview` is set to `false` in the PostHog config
|
|
4
|
+
* (the built-in capture only fires on initial page load, missing SPA navigations).
|
|
5
|
+
*/
|
|
6
|
+
export declare function PostHogPageviewTracker(): null;
|
|
7
|
+
//# sourceMappingURL=post-hog-pageview-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-pageview-tracker.d.ts","sourceRoot":"","sources":["../src/post-hog-pageview-tracker.tsx"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,sBAAsB,SASrC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useLocation } from "react-router";
|
|
3
|
+
import { usePostHog } from "posthog-js/react";
|
|
4
|
+
/**
|
|
5
|
+
* Captures `$pageview` events on client-side route changes.
|
|
6
|
+
* Required because `capture_pageview` is set to `false` in the PostHog config
|
|
7
|
+
* (the built-in capture only fires on initial page load, missing SPA navigations).
|
|
8
|
+
*/
|
|
9
|
+
export function PostHogPageviewTracker() {
|
|
10
|
+
const location = useLocation();
|
|
11
|
+
const posthog = usePostHog();
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
posthog.capture("$pageview");
|
|
14
|
+
}, [location.pathname, location.search, posthog]);
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=post-hog-pageview-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-pageview-tracker.js","sourceRoot":"","sources":["../src/post-hog-pageview-tracker.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IACpC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,OAAO,GAAG,UAAU,EAAE,CAAA;IAE5B,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9B,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAEjD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PostHogConfig } from "posthog-js";
|
|
2
|
+
interface PostHogProviderProps {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
options?: Partial<PostHogConfig>;
|
|
5
|
+
user?: {
|
|
6
|
+
distinctId: string;
|
|
7
|
+
properties?: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* All-in-one PostHog provider for React Router apps.
|
|
13
|
+
* Wraps children with PostHog context, tracks SPA pageviews, and identifies users.
|
|
14
|
+
* If `apiKey` is omitted, renders children without any PostHog functionality.
|
|
15
|
+
*/
|
|
16
|
+
export declare function PostHogProvider({ apiKey, options, user, children, }: PostHogProviderProps): import("react").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=post-hog-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-provider.d.ts","sourceRoot":"","sources":["../src/post-hog-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAK/C,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;IACnE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,GACT,EAAE,oBAAoB,+BAqBtB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PostHogProvider as PHProvider } from "posthog-js/react";
|
|
3
|
+
import { PostHogPageviewTracker } from "./post-hog-pageview-tracker.js";
|
|
4
|
+
import { PostHogIdentifier } from "./post-hog-identifier.js";
|
|
5
|
+
/**
|
|
6
|
+
* All-in-one PostHog provider for React Router apps.
|
|
7
|
+
* Wraps children with PostHog context, tracks SPA pageviews, and identifies users.
|
|
8
|
+
* If `apiKey` is omitted, renders children without any PostHog functionality.
|
|
9
|
+
*/
|
|
10
|
+
export function PostHogProvider({ apiKey, options, user, children, }) {
|
|
11
|
+
if (!apiKey) {
|
|
12
|
+
return _jsx(_Fragment, { children: children });
|
|
13
|
+
}
|
|
14
|
+
return (_jsxs(PHProvider, { apiKey: apiKey, options: {
|
|
15
|
+
...options,
|
|
16
|
+
capture_pageview: false,
|
|
17
|
+
}, children: [_jsx(PostHogPageviewTracker, {}), _jsx(PostHogIdentifier, { distinctId: user?.distinctId, properties: user?.properties }), children] }));
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=post-hog-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-hog-provider.js","sourceRoot":"","sources":["../src/post-hog-provider.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,IAAI,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAS5D;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,MAAM,EACN,OAAO,EACP,IAAI,EACJ,QAAQ,GACa;IACrB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,4BAAG,QAAQ,GAAI,CAAA;IACxB,CAAC;IAED,OAAO,CACL,MAAC,UAAU,IACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;YACP,GAAG,OAAO;YACV,gBAAgB,EAAE,KAAK;SACxB,aAED,KAAC,sBAAsB,KAAG,EAC1B,KAAC,iBAAiB,IAChB,UAAU,EAAE,IAAI,EAAE,UAAU,EAC5B,UAAU,EAAE,IAAI,EAAE,UAAU,GAC5B,EACD,QAAQ,IACE,CACd,CAAA;AACH,CAAC"}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
interface PostHogProxyOptions {
|
|
2
|
+
/**
|
|
3
|
+
* PostHog API origin, e.g. `"https://us.i.posthog.com"`. Full origin, not a
|
|
4
|
+
* region — the region choice stays explicit at the call site.
|
|
5
|
+
*/
|
|
6
|
+
apiHost: string;
|
|
7
|
+
/**
|
|
8
|
+
* PostHog static-assets origin. Defaults to `apiHost`. PostHog cloud serves
|
|
9
|
+
* assets from `<region>-assets.i.posthog.com`; pass it explicitly when the
|
|
10
|
+
* `static/` prefix must not hit the API origin.
|
|
11
|
+
*/
|
|
12
|
+
assetsHost?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Path prefix this route is mounted at. Defaults to `"/ph"`.
|
|
15
|
+
*/
|
|
16
|
+
mountPath?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum proxied request body in bytes, enforced BEFORE buffering (the
|
|
19
|
+
* Content-Length header is rejected first, then the stream is accumulated
|
|
20
|
+
* with a running cap). Defaults to 1 MiB — events are kilobytes.
|
|
21
|
+
*/
|
|
22
|
+
maxBodyBytes?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Upstream timeout in milliseconds. Defaults to 10s; a hung PostHog must
|
|
25
|
+
* never pin server resources.
|
|
26
|
+
*/
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Forward the incoming `X-Forwarded-For` header unchanged so PostHog sees
|
|
30
|
+
* the client IP chain (set by your ingress, used for geoIP). Defaults to
|
|
31
|
+
* false — no IP signal leaves your infrastructure unless you opt in.
|
|
32
|
+
* Spoofing caveat: clients can prepend entries to the chain; PostHog
|
|
33
|
+
* parses it per its own rules, same as every PostHog proxy setup.
|
|
34
|
+
*/
|
|
35
|
+
forwardIp?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates React Router `loader` and `action` handlers that proxy requests to
|
|
39
|
+
* PostHog. Use this in a splat route (e.g. `ph.$.ts`) to reverse-proxy all
|
|
40
|
+
* PostHog requests through your own domain, preventing CSP/CORS issues and ad
|
|
41
|
+
* blocker interference.
|
|
42
|
+
*
|
|
43
|
+
* Hardening (this route is same-origin, so browsers attach your session
|
|
44
|
+
* cookie to every `/ph` request):
|
|
45
|
+
*
|
|
46
|
+
* - Upstream origins are fixed at configuration time, never derived from
|
|
47
|
+
* request input.
|
|
48
|
+
* - `Cookie` and `Authorization` headers are stripped before proxying; only a
|
|
49
|
+
* deliberate allowlist of headers is forwarded.
|
|
50
|
+
* - Response `Set-Cookie` is stripped so PostHog can never plant cookies on
|
|
51
|
+
* your domain.
|
|
52
|
+
* - Request bodies are capped and the upstream call has a timeout.
|
|
53
|
+
* - Bodies are never logged or echoed back in error responses.
|
|
54
|
+
* - Client IPs are NOT forwarded by default; pass `forwardIp: true` to send
|
|
55
|
+
* the ingress-set `X-Forwarded-For` chain upstream for geoIP.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* // app/routes/ph.$.ts
|
|
60
|
+
* import { createPostHogProxy } from "@activescott/analytics/proxy"
|
|
61
|
+
* export const { loader, action } = createPostHogProxy({
|
|
62
|
+
* apiHost: "https://us.i.posthog.com",
|
|
63
|
+
* assetsHost: "https://us-assets.i.posthog.com",
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function createPostHogProxy(options: PostHogProxyOptions): {
|
|
68
|
+
loader: (loaderArguments: {
|
|
69
|
+
request: Request;
|
|
70
|
+
}) => Promise<Response>;
|
|
71
|
+
action: (actionArguments: {
|
|
72
|
+
request: Request;
|
|
73
|
+
}) => Promise<Response>;
|
|
74
|
+
};
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAiCA,UAAU,mBAAmB;IAC3B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG;IAChE,MAAM,EAAE,CAAC,eAAe,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpE,MAAM,EAAE,CAAC,eAAe,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACrE,CA0HA"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
const HTTP_NO_CONTENT = 204;
|
|
2
|
+
const HTTP_NOT_MODIFIED = 304;
|
|
3
|
+
const HTTP_NOT_FOUND = 404;
|
|
4
|
+
const HTTP_METHOD_NOT_ALLOWED = 405;
|
|
5
|
+
const HTTP_PAYLOAD_TOO_LARGE = 413;
|
|
6
|
+
const HTTP_BAD_GATEWAY = 502;
|
|
7
|
+
const HTTP_GATEWAY_TIMEOUT = 504;
|
|
8
|
+
const HTTP_INTERNAL_SERVER_ERROR = 500;
|
|
9
|
+
const DEFAULT_MOUNT_PATH = "/ph";
|
|
10
|
+
const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
12
|
+
/**
|
|
13
|
+
* First path segments (after the mount prefix) that may be proxied. Everything
|
|
14
|
+
* else answers 404 so this route can never become a generic relay. Covers
|
|
15
|
+
* posthog-js capture (`e`, `batch`), flags (`decide`, `flags`), session
|
|
16
|
+
* replay (`s`), surveys (`surveys`), autotrack/toolbar helpers (`array`) and
|
|
17
|
+
* static assets (`static`, routed to the assets host). Widen with care.
|
|
18
|
+
*/
|
|
19
|
+
const ALLOWED_PREFIXES = [
|
|
20
|
+
"e",
|
|
21
|
+
"batch",
|
|
22
|
+
"decide",
|
|
23
|
+
"flags",
|
|
24
|
+
"s",
|
|
25
|
+
"surveys",
|
|
26
|
+
"array",
|
|
27
|
+
"static",
|
|
28
|
+
];
|
|
29
|
+
const ALLOWED_METHODS = ["GET", "POST", "OPTIONS"];
|
|
30
|
+
/**
|
|
31
|
+
* Creates React Router `loader` and `action` handlers that proxy requests to
|
|
32
|
+
* PostHog. Use this in a splat route (e.g. `ph.$.ts`) to reverse-proxy all
|
|
33
|
+
* PostHog requests through your own domain, preventing CSP/CORS issues and ad
|
|
34
|
+
* blocker interference.
|
|
35
|
+
*
|
|
36
|
+
* Hardening (this route is same-origin, so browsers attach your session
|
|
37
|
+
* cookie to every `/ph` request):
|
|
38
|
+
*
|
|
39
|
+
* - Upstream origins are fixed at configuration time, never derived from
|
|
40
|
+
* request input.
|
|
41
|
+
* - `Cookie` and `Authorization` headers are stripped before proxying; only a
|
|
42
|
+
* deliberate allowlist of headers is forwarded.
|
|
43
|
+
* - Response `Set-Cookie` is stripped so PostHog can never plant cookies on
|
|
44
|
+
* your domain.
|
|
45
|
+
* - Request bodies are capped and the upstream call has a timeout.
|
|
46
|
+
* - Bodies are never logged or echoed back in error responses.
|
|
47
|
+
* - Client IPs are NOT forwarded by default; pass `forwardIp: true` to send
|
|
48
|
+
* the ingress-set `X-Forwarded-For` chain upstream for geoIP.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* // app/routes/ph.$.ts
|
|
53
|
+
* import { createPostHogProxy } from "@activescott/analytics/proxy"
|
|
54
|
+
* export const { loader, action } = createPostHogProxy({
|
|
55
|
+
* apiHost: "https://us.i.posthog.com",
|
|
56
|
+
* assetsHost: "https://us-assets.i.posthog.com",
|
|
57
|
+
* })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function createPostHogProxy(options) {
|
|
61
|
+
const apiHost = normalizeOrigin(options.apiHost);
|
|
62
|
+
const assetsHost = normalizeOrigin(options.assetsHost ?? options.apiHost);
|
|
63
|
+
const mountPath = (options.mountPath ?? DEFAULT_MOUNT_PATH).replace(/\/$/, "");
|
|
64
|
+
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
65
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
66
|
+
const forwardIp = options.forwardIp ?? false;
|
|
67
|
+
async function proxyRequest(request) {
|
|
68
|
+
if (!ALLOWED_METHODS.includes(request.method)) {
|
|
69
|
+
return new Response("Method Not Allowed", {
|
|
70
|
+
status: HTTP_METHOD_NOT_ALLOWED,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
let pathname;
|
|
74
|
+
try {
|
|
75
|
+
const url = new URL(request.url);
|
|
76
|
+
pathname = url.pathname;
|
|
77
|
+
if (!pathname.startsWith(`${mountPath}/`) && pathname !== mountPath) {
|
|
78
|
+
return new Response("Not Found", { status: HTTP_NOT_FOUND });
|
|
79
|
+
}
|
|
80
|
+
const stripped = pathname.slice(mountPath.length).replace(/^\//, "");
|
|
81
|
+
const firstSegment = stripped.split("/", 1)[0] ?? "";
|
|
82
|
+
const isAllowed = ALLOWED_PREFIXES.includes(firstSegment);
|
|
83
|
+
if (!isAllowed) {
|
|
84
|
+
return new Response("Not Found", { status: HTTP_NOT_FOUND });
|
|
85
|
+
}
|
|
86
|
+
const targetHost = firstSegment === "static" ? assetsHost : apiHost;
|
|
87
|
+
const targetUrl = new URL(`${stripped}${url.search}`, `${targetHost}/`);
|
|
88
|
+
const headers = new Headers();
|
|
89
|
+
const contentType = request.headers.get("content-type");
|
|
90
|
+
// Accept whatever content type the client sent (posthog-js uses
|
|
91
|
+
// `text/plain` for sendBeacon fallbacks) — the body is opaque bytes.
|
|
92
|
+
if (contentType) {
|
|
93
|
+
headers.set("content-type", contentType);
|
|
94
|
+
}
|
|
95
|
+
const userAgent = request.headers.get("user-agent");
|
|
96
|
+
if (userAgent) {
|
|
97
|
+
headers.set("user-agent", userAgent);
|
|
98
|
+
}
|
|
99
|
+
// Opt-in only: pass the ingress-set client IP chain through for
|
|
100
|
+
// PostHog geoIP. Forwarded unchanged (never synthesized here) and never
|
|
101
|
+
// logged. See the `forwardIp` option docs for the spoofing caveat.
|
|
102
|
+
if (forwardIp) {
|
|
103
|
+
const forwardedFor = request.headers.get("x-forwarded-for");
|
|
104
|
+
if (forwardedFor) {
|
|
105
|
+
headers.set("x-forwarded-for", forwardedFor);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Deliberately NOT forwarded: Cookie, Authorization, and everything
|
|
109
|
+
// else.
|
|
110
|
+
let body;
|
|
111
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
112
|
+
const contentLength = request.headers.get("content-length");
|
|
113
|
+
if (contentLength && Number(contentLength) > maxBodyBytes) {
|
|
114
|
+
return new Response("Payload Too Large", {
|
|
115
|
+
status: HTTP_PAYLOAD_TOO_LARGE,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
body = await readCappedBody(request.body, maxBodyBytes);
|
|
119
|
+
if (!body) {
|
|
120
|
+
return new Response("Payload Too Large", {
|
|
121
|
+
status: HTTP_PAYLOAD_TOO_LARGE,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const phResponse = await fetch(targetUrl.toString(), {
|
|
126
|
+
method: request.method,
|
|
127
|
+
headers,
|
|
128
|
+
body,
|
|
129
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
130
|
+
});
|
|
131
|
+
if (phResponse.status === HTTP_NO_CONTENT ||
|
|
132
|
+
phResponse.status === HTTP_NOT_MODIFIED ||
|
|
133
|
+
!phResponse.body) {
|
|
134
|
+
return new Response(null, {
|
|
135
|
+
status: phResponse.status,
|
|
136
|
+
statusText: phResponse.statusText,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const responseHeaders = new Headers(phResponse.headers);
|
|
140
|
+
responseHeaders.delete("content-encoding");
|
|
141
|
+
responseHeaders.delete("content-length");
|
|
142
|
+
responseHeaders.delete("set-cookie");
|
|
143
|
+
return new Response(phResponse.body, {
|
|
144
|
+
status: phResponse.status,
|
|
145
|
+
statusText: phResponse.statusText,
|
|
146
|
+
headers: responseHeaders,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof DOMException && error.name === "TimeoutError") {
|
|
151
|
+
return new Response("Gateway Timeout", {
|
|
152
|
+
status: HTTP_GATEWAY_TIMEOUT,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
156
|
+
return new Response("Gateway Timeout", {
|
|
157
|
+
status: HTTP_GATEWAY_TIMEOUT,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return new Response("Bad Gateway", { status: HTTP_BAD_GATEWAY });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
loader({ request }) {
|
|
165
|
+
return proxyRequest(request);
|
|
166
|
+
},
|
|
167
|
+
action({ request }) {
|
|
168
|
+
return proxyRequest(request);
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function normalizeOrigin(origin) {
|
|
173
|
+
const url = new URL(origin);
|
|
174
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
175
|
+
throw new Error(`PostHog origin must be http(s): received ${url.protocol}`);
|
|
176
|
+
}
|
|
177
|
+
return url.origin;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Accumulates a request stream up to `maxBytes`. Returns undefined when the
|
|
181
|
+
* body exceeds the cap (caller answers 413 without ever buffering it whole).
|
|
182
|
+
*/
|
|
183
|
+
async function readCappedBody(stream, maxBytes) {
|
|
184
|
+
if (!stream) {
|
|
185
|
+
return new Uint8Array(0);
|
|
186
|
+
}
|
|
187
|
+
const reader = stream.getReader();
|
|
188
|
+
const chunks = [];
|
|
189
|
+
let total = 0;
|
|
190
|
+
try {
|
|
191
|
+
for (;;) {
|
|
192
|
+
const { done, value } = await reader.read();
|
|
193
|
+
if (done) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
total += value.byteLength;
|
|
197
|
+
if (total > maxBytes) {
|
|
198
|
+
await reader.cancel().catch(() => { });
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
chunks.push(value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
reader.releaseLock();
|
|
206
|
+
}
|
|
207
|
+
const merged = new Uint8Array(total);
|
|
208
|
+
let offset = 0;
|
|
209
|
+
for (const chunk of chunks) {
|
|
210
|
+
merged.set(chunk, offset);
|
|
211
|
+
offset += chunk.byteLength;
|
|
212
|
+
}
|
|
213
|
+
return merged;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@activescott/analytics",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "PostHog analytics integration for React Router apps — provider, SPA pageview tracking, user identification, and reverse proxy helper",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./proxy": {
|
|
14
|
+
"types": "./dist/proxy.d.ts",
|
|
15
|
+
"import": "./dist/proxy.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"posthog",
|
|
28
|
+
"analytics",
|
|
29
|
+
"react-router",
|
|
30
|
+
"spa",
|
|
31
|
+
"pageview",
|
|
32
|
+
"proxy"
|
|
33
|
+
],
|
|
34
|
+
"author": "Scott Willeke",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/activescott/js-utils.git",
|
|
39
|
+
"directory": "packages/analytics"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"posthog-js": "^1.200.0",
|
|
43
|
+
"react": ">=18",
|
|
44
|
+
"react-router": "^7.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22",
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
|
+
"posthog-js": "^1.351.4",
|
|
50
|
+
"react": "^19.2.4",
|
|
51
|
+
"react-router": "^7.10.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "4.1.10"
|
|
54
|
+
}
|
|
55
|
+
}
|