Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@posthog/plugin-unduplicates

Package Overview
Dependencies
Maintainers
8
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@posthog/plugin-unduplicates - npm Package Compare versions

Comparing version
0.0.2
to
0.0.3
+14
.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'simple-import-sort'],
extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
ignorePatterns: ['bin', 'dist', 'node_modules'],
rules: {
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
curly: 'error',
},
}
name: CI
on:
- pull_request
jobs:
lint:
name: Code formatting & linting
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
- name: Set up Node 16
uses: actions/setup-node@v1
with:
node-version: 16
- uses: actions/cache@v2
id: node-modules-cache
with:
path: |
node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-modules
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
- name: Check formatting with Prettier
run: yarn format:check
- name: Lint with ESLint
run: yarn lint
- name: Check Typescript
run: |
yarn typescript:check
test:
name: Test
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v1
- name: Set up Node 16
uses: actions/setup-node@v1
with:
node-version: 16
- uses: actions/cache@v2
id: node-modules-cache
with:
path: |
node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-modules
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
run: yarn install --frozen-lockfile
- name: Run test
run: |
yarn test
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"printWidth": 120
}
{
"editor.formatOnSave": true
}
releases:
"@posthog/plugin-unduplicates": patch
/* eslint-disable no-var */
interface ApiMethodOptions {
data?: Record<string, any> // any data to send with the request, GET and DELETE will set these as URL params
host?: string // posthog host, defaults to https://app.posthog.com
projectApiKey?: string // specifies the project to interact with
personalApiKey?: string // authenticates the user
}
interface APIInterface {
get: (path: string, options?: ApiMethodOptions) => Promise<Record<string, any>>
}
declare namespace posthog {
var api: APIInterface
}
import { Plugin, PluginMeta } from '@posthog/plugin-scaffold'
// @ts-ignore
import { createPageview, resetMeta } from '@posthog/plugin-scaffold/test/utils'
import { createHash } from 'crypto'
import given from 'given2'
import * as unduplicatesPlugin from '.'
const { processEvent } = unduplicatesPlugin as Required<Plugin>
given('getResponse', () => ({ results: [], next: null }))
global.posthog = {
api: {
get: jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(given.getResponse),
})
),
},
}
const defaultMeta = {
config: {
dedupMode: 'Event and Timestamp',
},
}
describe('`Event and Timestamp` mode', () => {
test('event UUID is properly generated', async () => {
const meta = resetMeta() as PluginMeta<Plugin>
const event = await processEvent({ ...createPageview(), timestamp: '2020-02-02T23:59:59.999999Z' }, meta)
expect(event?.uuid).toEqual('1b2b7e1a-c059-5116-a6d2-eb1c1dd793bc')
})
test('same key properties produces the same UUID', async () => {
const meta = resetMeta() as PluginMeta<Plugin>
const event1 = await processEvent(
{ ...createPageview(), event: 'myPageview', timestamp: '2020-05-02T20:59:59.999999Z', ignoreMe: 'yes' },
meta
)
const event2 = await processEvent(
{
...createPageview(),
event: 'myPageview',
timestamp: '2020-05-02T20:59:59.999999Z',
differentProperty: 'indeed',
},
meta
)
expect(event1?.uuid).toBeTruthy()
expect(event1?.uuid).toEqual(event2?.uuid)
})
test('different key properties produces a different UUID', async () => {
const meta = resetMeta() as PluginMeta<Plugin>
const event1 = await processEvent({ ...createPageview(), timestamp: '2020-05-02T20:59:59.999999Z' }, meta)
const event2 = await processEvent(
{
...createPageview(),
timestamp: '2020-05-02T20:59:59.999888Z', // note milliseconds are different
},
meta
)
expect(event1?.uuid).toBeTruthy()
expect(event1?.uuid).not.toEqual(event2?.uuid)
})
})
describe('`All Properties` mode', () => {
test('event UUID is properly generated (all props)', async () => {
const meta = resetMeta({
config: { ...defaultMeta.config, dedupMode: 'All Properties' },
}) as PluginMeta<Plugin>
const event = await processEvent({ ...createPageview(), timestamp: '2020-02-02T23:59:59.999999Z' }, meta)
expect(event?.uuid).toEqual('5a4e6d35-a9e4-50e2-9d97-4f7cc04e8b30')
})
test('same key properties produces the same UUID (all props)', async () => {
const meta = resetMeta({
config: { ...defaultMeta.config, dedupMode: 'All Properties' },
}) as PluginMeta<Plugin>
const event1 = await processEvent(
{
...createPageview(),
event: 'myPageview',
timestamp: '2020-05-02T20:59:59.999999Z',
properties: {
...createPageview().properties,
customProp1: true,
customProp2: 'lgtm!',
},
},
meta
)
const event2 = await processEvent(
{
...createPageview(),
event: 'myPageview',
timestamp: '2020-05-02T20:59:59.999999Z',
properties: {
...createPageview().properties,
customProp1: true,
customProp2: 'lgtm!',
},
},
meta
)
expect(event1?.uuid).toBeTruthy()
expect(event1?.uuid).toEqual(event2?.uuid)
})
test('different properties produce a different UUID (all props)', async () => {
const meta = resetMeta({
config: { ...defaultMeta.config, dedupMode: 'All Properties' },
}) as PluginMeta<Plugin>
const event1 = await processEvent(
{ ...createPageview(), timestamp: '2020-05-02T20:59:59.999999Z', properties: { customProp: '2' } },
meta
)
const event2 = await processEvent(
{
...createPageview(),
timestamp: '2020-05-02T20:59:59.999999Z',
properties: { customProp: '1' },
},
meta
)
expect(event1?.uuid).toBeTruthy()
expect(event1?.uuid).not.toEqual(event2?.uuid)
})
})
import { Plugin, PluginEvent } from '@posthog/plugin-scaffold'
import { createHash, randomUUID } from 'crypto'
// From UUID Namespace RFC (https://datatracker.ietf.org/doc/html/rfc4122)
const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8'
interface UnduplicatesPluginInterface {
config: {
dedupMode: 'Event and Timestamp' | 'All Properties'
}
}
const stringifyEvent = (event: PluginEvent): string => {
return `(${randomUUID().toString()}; project #${event.team_id}). Event "${event.event}" @ ${
event.timestamp
} for user ${event.distinct_id}.`
}
const byteToHex: string[] = []
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1))
}
function stringifyUUID(arr: Buffer) {
// Forked from https://github.com/uuidjs/uuid (MIT)
// Copyright (c) 2010-2020 Robert Kieffer and other contributors
return (
byteToHex[arr[0]] +
byteToHex[arr[1]] +
byteToHex[arr[2]] +
byteToHex[arr[3]] +
'-' +
byteToHex[arr[4]] +
byteToHex[arr[5]] +
'-' +
byteToHex[arr[6]] +
byteToHex[arr[7]] +
'-' +
byteToHex[arr[8]] +
byteToHex[arr[9]] +
'-' +
byteToHex[arr[10]] +
byteToHex[arr[11]] +
byteToHex[arr[12]] +
byteToHex[arr[13]] +
byteToHex[arr[14]] +
byteToHex[arr[15]]
).toLowerCase()
}
const plugin: Plugin<UnduplicatesPluginInterface> = {
processEvent: async (event, { config }) => {
const stringifiedEvent = stringifyEvent(event)
if (!event.timestamp) {
console.info(
'Received event without a timestamp, the event will not be processed because deduping will not work.'
)
return event
}
// Create a hash of the relevant properties of the event
const stringifiedProps = config.dedupMode === 'All Properties' ? `_${JSON.stringify(event.properties)}` : ''
const hash = createHash('sha1')
const eventKeyBuffer = hash
.update(
`${NAMESPACE_OID}_${event.team_id}_${event.distinct_id}_${event.event}_${event.timestamp}${stringifiedProps}`
)
.digest()
// Convert to UUID v5 spec
eventKeyBuffer[6] = (eventKeyBuffer[6] & 0x0f) | 0x50
eventKeyBuffer[8] = (eventKeyBuffer[8] & 0x3f) | 0x80
event.uuid = stringifyUUID(eventKeyBuffer)
return event
},
}
module.exports = plugin
const { pathsToModuleNameMapper } = require('ts-jest/utils')
const { compilerOptions } = require('./tsconfig')
const moduleNameMapper = undefined
if (compilerOptions.paths) {
moduleNameMapper = pathsToModuleNameMapper(compilerOptions.paths, { prefix: 'src/' })
}
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper,
setupFilesAfterEnv: ['given2/setup'],
}
{
"extends": "../../../tsconfig.json"
}
+2
-2
MIT License
Copyright (c) 2022 PostHog, Inc.
Copyright (c) 2022 Paolo D'Amico

@@ -21,2 +21,2 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
{
"name": "@posthog/plugin-unduplicates",
"version": "0.0.2",
"version": "0.0.3",
"description": "Prevent duplicates in your data by rejecting duplicate events at ingestion.",

@@ -5,0 +5,0 @@ "main": "index.ts",

@@ -5,3 +5,3 @@ {

"description": "Prevent duplicates in your data when ingesting.",
"main": "index.js",
"main": "index.ts",
"posthogVersion": ">=1.25.0",

@@ -8,0 +8,0 @@ "config": [

import { createHash, randomUUID } from 'crypto';
const NAMESPACE_OID = '6ba7b812-9dad-11d1-80b4-00c04fd430c8';
const stringifyEvent = (event) => {
return `(${randomUUID().toString()}; project #${event.team_id}). Event "${event.event}" @ ${event.timestamp} for user ${event.distinct_id}.`;
};
const byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 0x100).toString(16).slice(1));
}
function stringifyUUID(arr) {
return (byteToHex[arr[0]] +
byteToHex[arr[1]] +
byteToHex[arr[2]] +
byteToHex[arr[3]] +
'-' +
byteToHex[arr[4]] +
byteToHex[arr[5]] +
'-' +
byteToHex[arr[6]] +
byteToHex[arr[7]] +
'-' +
byteToHex[arr[8]] +
byteToHex[arr[9]] +
'-' +
byteToHex[arr[10]] +
byteToHex[arr[11]] +
byteToHex[arr[12]] +
byteToHex[arr[13]] +
byteToHex[arr[14]] +
byteToHex[arr[15]]).toLowerCase();
}
const plugin = {
processEvent: async (event, { config }) => {
stringifyEvent(event);
if (!event.timestamp) {
console.info('Received event without a timestamp, the event will not be processed because deduping will not work.');
return event;
}
const stringifiedProps = config.dedupMode === 'All Properties' ? `_${JSON.stringify(event.properties)}` : '';
const hash = createHash('sha1');
const eventKeyBuffer = hash
.update(`${NAMESPACE_OID}_${event.team_id}_${event.distinct_id}_${event.event}_${event.timestamp}${stringifiedProps}`)
.digest();
eventKeyBuffer[6] = (eventKeyBuffer[6] & 0x0f) | 0x50;
eventKeyBuffer[8] = (eventKeyBuffer[8] & 0x3f) | 0x80;
event.uuid = stringifyUUID(eventKeyBuffer);
return event;
},
};
module.exports = plugin;