mirror of
https://github.com/papra-hq/papra.git
synced 2026-01-06 08:59:37 -06:00
feat(client): added posthog analytics (#151)
This commit is contained in:
committed by
GitHub
parent
2fd681b8a4
commit
3e7b4ea2db
@@ -48,6 +48,7 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"ofetch": "^1.4.1",
|
||||
"plausible-tracker": "^0.3.9",
|
||||
"posthog-js": "^1.231.0",
|
||||
"radix3": "^1.1.2",
|
||||
"solid-js": "^1.8.11",
|
||||
"solid-sonner": "^0.2.8",
|
||||
|
||||
@@ -10,9 +10,10 @@ import { CommandPaletteProvider } from './modules/command-palette/command-palett
|
||||
import { ConfigProvider } from './modules/config/config.provider';
|
||||
import { DemoIndicator } from './modules/demo/demo.provider';
|
||||
import { I18nProvider } from './modules/i18n/i18n.provider';
|
||||
import { PlausibleTracker } from './modules/plausible/components/plausible-tracker.component';
|
||||
import { ConfirmModalProvider } from './modules/shared/confirm';
|
||||
import { queryClient } from './modules/shared/query/query-client';
|
||||
import { IdentifyUser } from './modules/tracking/components/identify-user.component';
|
||||
import { PageViewTracker } from './modules/tracking/components/pageview-tracker.component';
|
||||
import { Toaster } from './modules/ui/components/sonner';
|
||||
import { routes } from './routes';
|
||||
import '@unocss/reset/tailwind.css';
|
||||
@@ -30,7 +31,9 @@ render(
|
||||
children={routes}
|
||||
root={props => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<PlausibleTracker />
|
||||
<PageViewTracker />
|
||||
<IdentifyUser />
|
||||
|
||||
<Suspense>
|
||||
<I18nProvider>
|
||||
<ConfirmModalProvider>
|
||||
|
||||
@@ -2,13 +2,25 @@ import { organizationClient as organizationClientPlugin } from 'better-auth/clie
|
||||
import { createAuthClient as createBetterAuthClient } from 'better-auth/solid';
|
||||
|
||||
import { buildTimeConfig } from '../config/config';
|
||||
import { trackingServices } from '../tracking/tracking.services';
|
||||
import { createDemoAuthClient } from './auth.demo.services';
|
||||
|
||||
export function createAuthClient() {
|
||||
return createBetterAuthClient({
|
||||
const client = createBetterAuthClient({
|
||||
baseURL: buildTimeConfig.baseApiUrl,
|
||||
plugins: [organizationClientPlugin()],
|
||||
});
|
||||
|
||||
return {
|
||||
...client,
|
||||
signOut: async () => {
|
||||
trackingServices.capture({ event: 'User logged out' });
|
||||
const result = await client.signOut();
|
||||
trackingServices.reset();
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const {
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
export const isDev = import.meta.env.MODE === 'development';
|
||||
|
||||
const asBoolean = (value: string | undefined, defaultValue: boolean) => value === undefined ? defaultValue : value.trim().toLowerCase() === 'true';
|
||||
const asString = <T extends string | undefined>(value: string | undefined, defaultValue?: T): T extends undefined ? string | undefined : string => (value ?? defaultValue) as T extends undefined ? string | undefined : string;
|
||||
const asNumber = <T extends number | undefined>(value: string | undefined, defaultValue?: T): T extends undefined ? number | undefined : number => (value === undefined ? defaultValue : Number(value)) as T extends undefined ? number | undefined : number;
|
||||
|
||||
export const buildTimeConfig = {
|
||||
papraVersion: import.meta.env.VITE_PAPRA_VERSION,
|
||||
baseUrl: (import.meta.env.VITE_BASE_URL ?? window.location.origin) as string,
|
||||
baseApiUrl: (import.meta.env.VITE_BASE_API_URL ?? window.location.origin) as string,
|
||||
vitrineBaseUrl: (import.meta.env.VITE_VITRINE_BASE_URL ?? 'http://localhost:3000/') as string,
|
||||
isDemoMode: import.meta.env.VITE_IS_DEMO_MODE === 'true',
|
||||
papraVersion: asString(import.meta.env.VITE_PAPRA_VERSION),
|
||||
baseUrl: asString(import.meta.env.VITE_BASE_URL, window.location.origin),
|
||||
baseApiUrl: asString(import.meta.env.VITE_BASE_API_URL, window.location.origin),
|
||||
vitrineBaseUrl: asString(import.meta.env.VITE_VITRINE_BASE_URL, 'http://localhost:3000/'),
|
||||
isDemoMode: asBoolean(import.meta.env.VITE_IS_DEMO_MODE, false),
|
||||
auth: {
|
||||
isRegistrationEnabled: import.meta.env.VITE_AUTH_IS_REGISTRATION_ENABLED !== 'false',
|
||||
isPasswordResetEnabled: import.meta.env.VITE_AUTH_IS_PASSWORD_RESET_ENABLED !== 'false',
|
||||
isEmailVerificationRequired: import.meta.env.VITE_AUTH_IS_EMAIL_VERIFICATION_REQUIRED !== 'false',
|
||||
showLegalLinksOnAuthPage: import.meta.env.VITE_AUTH_SHOW_LEGAL_LINKS_ON_AUTH_PAGE === 'true',
|
||||
isRegistrationEnabled: asBoolean(import.meta.env.VITE_AUTH_IS_REGISTRATION_ENABLED, true),
|
||||
isPasswordResetEnabled: asBoolean(import.meta.env.VITE_AUTH_IS_PASSWORD_RESET_ENABLED, true),
|
||||
isEmailVerificationRequired: asBoolean(import.meta.env.VITE_AUTH_IS_EMAIL_VERIFICATION_REQUIRED, true),
|
||||
showLegalLinksOnAuthPage: asBoolean(import.meta.env.VITE_AUTH_SHOW_LEGAL_LINKS_ON_AUTH_PAGE, false),
|
||||
providers: {
|
||||
github: {
|
||||
isEnabled: import.meta.env.VITE_AUTH_PROVIDERS_GITHUB_IS_ENABLED === 'true',
|
||||
},
|
||||
google: {
|
||||
isEnabled: import.meta.env.VITE_AUTH_PROVIDERS_GOOGLE_IS_ENABLED === 'true',
|
||||
},
|
||||
github: { isEnabled: asBoolean(import.meta.env.VITE_AUTH_PROVIDERS_GITHUB_IS_ENABLED, false) },
|
||||
google: { isEnabled: asBoolean(import.meta.env.VITE_AUTH_PROVIDERS_GOOGLE_IS_ENABLED, false) },
|
||||
},
|
||||
},
|
||||
documents: {
|
||||
deletedDocumentsRetentionDays: Number(import.meta.env.VITE_DOCUMENTS_DELETED_DOCUMENTS_RETENTION_DAYS ?? 30),
|
||||
deletedDocumentsRetentionDays: asNumber(import.meta.env.VITE_DOCUMENTS_DELETED_DOCUMENTS_RETENTION_DAYS, 30),
|
||||
},
|
||||
plausible: {
|
||||
isEnabled: import.meta.env.VITE_PLAUSIBLE_IS_ENABLED === 'true',
|
||||
domain: import.meta.env.VITE_PLAUSIBLE_DOMAIN,
|
||||
apiHost: import.meta.env.VITE_PLAUSIBLE_API_HOST,
|
||||
posthog: {
|
||||
apiKey: asString(import.meta.env.VITE_POSTHOG_API_KEY),
|
||||
host: asString(import.meta.env.VITE_POSTHOG_HOST),
|
||||
isEnabled: asBoolean(import.meta.env.VITE_POSTHOG_API_KEY, false),
|
||||
},
|
||||
intakeEmails: {
|
||||
isEnabled: import.meta.env.VITE_INTAKE_EMAILS_IS_ENABLED === 'true',
|
||||
emailGenerationDomain: import.meta.env.VITE_INTAKE_EMAILS_EMAIL_GENERATION_DOMAIN,
|
||||
isEnabled: asBoolean(import.meta.env.VITE_INTAKE_EMAILS_IS_ENABLED, false),
|
||||
emailGenerationDomain: asString(import.meta.env.VITE_INTAKE_EMAILS_EMAIL_GENERATION_DOMAIN),
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Organization } from '../organizations/organizations.types';
|
||||
import type { Tag } from '../tags/tags.types';
|
||||
import { createStorage, prefixStorage } from 'unstorage';
|
||||
import localStorageDriver from 'unstorage/drivers/localstorage';
|
||||
import { trackingServices } from '../tracking/tracking.services';
|
||||
|
||||
const storage = createStorage<any>({
|
||||
driver: localStorageDriver({ base: 'demo:' }),
|
||||
@@ -16,4 +17,5 @@ export const tagDocumentStorage = prefixStorage<{ documentId: string; tagId: str
|
||||
|
||||
export async function clearDemoStorage() {
|
||||
await storage.clear();
|
||||
trackingServices.capture({ event: 'Demo storage cleared' });
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { buildTimeConfig } from '@/modules/config/config';
|
||||
import { buildUrl } from '@corentinth/chisels';
|
||||
import { useCurrentMatches } from '@solidjs/router';
|
||||
import Plausible from 'plausible-tracker';
|
||||
import { type Component, createEffect } from 'solid-js';
|
||||
|
||||
export const PlausibleTracker: Component = () => {
|
||||
const { isEnabled, apiHost, domain } = buildTimeConfig.plausible;
|
||||
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const plausible = Plausible({
|
||||
domain,
|
||||
apiHost,
|
||||
trackLocalhost: false,
|
||||
});
|
||||
|
||||
const matches = useCurrentMatches();
|
||||
|
||||
createEffect(() => {
|
||||
const basePattern = matches().at(-1)?.route.pattern ?? '/';
|
||||
const pattern = basePattern === '*404' ? window.location.pathname : basePattern;
|
||||
|
||||
const url = buildUrl({
|
||||
path: pattern,
|
||||
baseUrl: window.location.origin,
|
||||
});
|
||||
|
||||
plausible.trackPageview({ url });
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useSession } from '@/modules/auth/auth.services';
|
||||
import { type Component, createEffect } from 'solid-js';
|
||||
import { trackingServices } from '../tracking.services';
|
||||
|
||||
export const IdentifyUser: Component = () => {
|
||||
const session = useSession();
|
||||
|
||||
createEffect(() => {
|
||||
const user = session()?.data?.user;
|
||||
|
||||
if (user) {
|
||||
trackingServices.identify({
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useCurrentMatches } from '@solidjs/router';
|
||||
import { type Component, createEffect, on } from 'solid-js';
|
||||
import { trackingServices } from '../tracking.services';
|
||||
|
||||
export const PageViewTracker: Component = () => {
|
||||
const matches = useCurrentMatches();
|
||||
|
||||
createEffect(on(matches, () => {
|
||||
trackingServices.capture({ event: '$pageview' });
|
||||
}));
|
||||
|
||||
return null;
|
||||
};
|
||||
62
apps/papra-client/src/modules/tracking/tracking.services.ts
Normal file
62
apps/papra-client/src/modules/tracking/tracking.services.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import posthog from 'posthog-js';
|
||||
import { buildTimeConfig, isDev } from '../config/config';
|
||||
|
||||
type TrackingServices = {
|
||||
capture: (args: {
|
||||
event: string;
|
||||
properties?: Record<string, unknown>;
|
||||
}) => void;
|
||||
|
||||
reset: () => void;
|
||||
|
||||
identify: (args: {
|
||||
userId: string;
|
||||
email: string;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
const dummyTrackingServices: TrackingServices = {
|
||||
capture: ({ event, ...args }) => {
|
||||
if (isDev) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[dev] captured event ${event}`, args);
|
||||
}
|
||||
},
|
||||
reset: () => {},
|
||||
identify: () => {},
|
||||
};
|
||||
|
||||
function createTrackingServices(): TrackingServices {
|
||||
const { isEnabled, apiKey, host } = buildTimeConfig.posthog;
|
||||
|
||||
if (!isEnabled) {
|
||||
return dummyTrackingServices;
|
||||
}
|
||||
|
||||
if (!apiKey) {
|
||||
console.warn('PostHog API key is not set');
|
||||
return dummyTrackingServices;
|
||||
}
|
||||
|
||||
posthog.init(
|
||||
apiKey,
|
||||
{
|
||||
api_host: host,
|
||||
capture_pageview: false,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
capture: ({ event, properties }) => {
|
||||
posthog.capture(event, properties);
|
||||
},
|
||||
reset: () => {
|
||||
posthog.reset();
|
||||
},
|
||||
identify: ({ userId, email }) => {
|
||||
posthog.identify(userId, { email });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const trackingServices = createTrackingServices();
|
||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@@ -138,6 +138,9 @@ importers:
|
||||
plausible-tracker:
|
||||
specifier: ^0.3.9
|
||||
version: 0.3.9
|
||||
posthog-js:
|
||||
specifier: ^1.231.0
|
||||
version: 1.231.0
|
||||
radix3:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2
|
||||
@@ -3479,6 +3482,9 @@ packages:
|
||||
core-js-compat@3.39.0:
|
||||
resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==}
|
||||
|
||||
core-js@3.41.0:
|
||||
resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4148,6 +4154,9 @@ packages:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
figue@2.2.0:
|
||||
resolution: {integrity: sha512-KOXaFezLrSuymCOo3c62iWcCF9SbDBxQv9WAX4bRTFAoKHQrTiEUZTdzbg07/AdqS8DZFEgIlCtc2myiY4CVmQ==}
|
||||
peerDependencies:
|
||||
@@ -5391,6 +5400,20 @@ packages:
|
||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
posthog-js@1.231.0:
|
||||
resolution: {integrity: sha512-8v3zRytQBg3KyKUPLy/9S5fw7ATeiKz3n3pLFxl1fQsV/a2mIt/MAwkIREZXTzi7mamsvtfXhSdggG7UYK/Ojw==}
|
||||
peerDependencies:
|
||||
'@rrweb/types': 2.0.0-alpha.17
|
||||
rrweb-snapshot: 2.0.0-alpha.17
|
||||
peerDependenciesMeta:
|
||||
'@rrweb/types':
|
||||
optional: true
|
||||
rrweb-snapshot:
|
||||
optional: true
|
||||
|
||||
preact@10.26.4:
|
||||
resolution: {integrity: sha512-KJhO7LBFTjP71d83trW+Ilnjbo+ySsaAgCfXOXUlmGzJ4ygYPWmysm77yg4emwfmoz3b22yvH5IsVFHbhUaH5w==}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6419,6 +6442,9 @@ packages:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
web-vitals@4.2.4:
|
||||
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
@@ -10511,6 +10537,8 @@ snapshots:
|
||||
dependencies:
|
||||
browserslist: 4.24.3
|
||||
|
||||
core-js@3.41.0: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@@ -11546,6 +11574,8 @@ snapshots:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
figue@2.2.0(zod@3.24.2):
|
||||
dependencies:
|
||||
zod: 3.24.2
|
||||
@@ -13282,6 +13312,15 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
posthog-js@1.231.0:
|
||||
dependencies:
|
||||
core-js: 3.41.0
|
||||
fflate: 0.4.8
|
||||
preact: 10.26.4
|
||||
web-vitals: 4.2.4
|
||||
|
||||
preact@10.26.4: {}
|
||||
|
||||
prebuild-install@7.1.3:
|
||||
dependencies:
|
||||
detect-libc: 2.0.3
|
||||
@@ -14494,6 +14533,8 @@ snapshots:
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
web-vitals@4.2.4: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
Reference in New Issue
Block a user