UNPKG

@posthog/geoip-plugin

Version:

Enrich PostHog events and persons with IP location data

116 lines (102 loc) 4.61 kB
import { Plugin } from '@posthog/plugin-scaffold' const ONE_DAY = 60 * 60 * 24 // 24h in seconds const defaultLocationSetProps = { $geoip_city_name: null, $geoip_country_name: null, $geoip_country_code: null, $geoip_continent_name: null, $geoip_continent_code: null, $geoip_postal_code: null, $geoip_latitude: null, $geoip_longitude: null, $geoip_time_zone: null, } const defaultLocationSetOnceProps = { $initial_geoip_city_name: null, $initial_geoip_country_name: null, $initial_geoip_country_code: null, $initial_geoip_continent_name: null, $initial_geoip_continent_code: null, $initial_geoip_postal_code: null, $initial_geoip_latitude: null, $initial_geoip_longitude: null, $initial_geoip_time_zone: null, } const plugin: Plugin = { processEvent: async (event, { geoip, cache }) => { if (!geoip) { throw new Error('This PostHog version does not have GeoIP capabilities! Upgrade to PostHog 1.24.0 or later') } let ip = event.properties?.$ip || event.ip if (ip && !event.properties?.$geoip_disable) { ip = String(ip) if (ip === '127.0.0.1') { ip = '13.106.122.3' // Spoofing an Australian IP address for local development } const response = await geoip.locate(ip) if (response) { const location: Record<string, any> = {} if (response.city) { location['city_name'] = response.city.names?.en } if (response.country) { location['country_name'] = response.country.names?.en location['country_code'] = response.country.isoCode } if (response.continent) { location['continent_name'] = response.continent.names?.en location['continent_code'] = response.continent.code } if (response.postal) { location['postal_code'] = response.postal.code } if (response.location) { location['latitude'] = response.location?.latitude location['longitude'] = response.location?.longitude location['time_zone'] = response.location?.timeZone } if (response.subdivisions) { for (const [index, subdivision] of response.subdivisions.entries()) { location[`subdivision_${index + 1}_code`] = subdivision.isoCode location[`subdivision_${index + 1}_name`] = subdivision.names?.en } } if (!event.properties) { event.properties = {} } let setPersonProps = true const lastIpSetEntry = await cache.get(event.distinct_id, null) if (typeof lastIpSetEntry === 'string') { const [lastIpSet, timestamp] = lastIpSetEntry.split('|') // New IP but this event is late and another event that happened after // but was received earlier has already updated the props const isEventSettingPropertiesLate = event.timestamp && timestamp && new Date(event.timestamp) < new Date(timestamp) // Person props update is not needed if the event's IP is the same as last set for the person if (lastIpSet === ip || isEventSettingPropertiesLate) { setPersonProps = false } } if (setPersonProps) { event.$set = { ...defaultLocationSetProps, ...(event.$set ?? {}) } event.$set_once = { ...defaultLocationSetOnceProps, ...(event.$set_once ?? {}), } } for (const [key, value] of Object.entries(location)) { event.properties[`$geoip_${key}`] = value if (setPersonProps) { event.$set![`$geoip_${key}`] = value event.$set_once![`$initial_geoip_${key}`] = value } } if (setPersonProps) { await cache.set(event.distinct_id, `${ip}|${event.timestamp || ''}`, ONE_DAY) } } } return event }, } module.exports = plugin