Socket
Socket
Sign inDemoInstall

posthog-node

Package Overview
Dependencies
Maintainers
6
Versions
66
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

posthog-node - npm Package Compare versions

Comparing version 2.2.3 to 2.3.0

6

CHANGELOG.md

@@ -0,1 +1,7 @@

# 2.3.0 - 2022-1-26
1. uses v3 decide endpoint
2. JSON payloads will be returned with feature flags
3. Feature flags will gracefully fail and optimistically save evaluated flags if server is down
# 2.2.3 - 2022-12-01

@@ -2,0 +8,0 @@ 1. Fix issues with timeouts for local evaluation requests

/// <reference types="node" />
import { JsonType as JsonType$1 } from 'posthog-core/src';
declare type PosthogCoreOptions = {

@@ -13,2 +15,3 @@ host?: string;

featureFlags?: Record<string, boolean | string>;
featureFlagPayloads?: Record<string, JsonType>;
};

@@ -36,2 +39,28 @@ fetchRetryCount?: number;

};
declare type PostHogDecideResponse = {
config: {
enable_collect_everything: boolean;
};
editorParams: {
toolbarVersion: string;
jsURL: string;
};
isAuthenticated: true;
supportedCompression: string[];
featureFlags: {
[key: string]: string | boolean;
};
featureFlagPayloads: {
[key: string]: JsonType;
};
errorsWhileComputingFlags: boolean;
sessionRecording: boolean;
};
declare type PosthogFlagsAndPayloadsResponse = {
featureFlags: PostHogDecideResponse['featureFlags'];
featureFlagPayloads: PostHogDecideResponse['featureFlagPayloads'];
};
declare type JsonType = string | number | boolean | null | {
[key: string]: JsonType;
} | Array<JsonType>;

@@ -135,2 +164,19 @@ interface IdentifyMessageV1 {

/**
* @description Retrieves payload associated with the specified flag and matched value that is passed in.
* (Expected to be used in conjuction with getFeatureFlag but allows for manual lookup).
* If matchValue isn't passed, getFeatureFlag is called implicitly.
* Will try to evaluate for payload locally first otherwise default to network call if allowed
*
* @param key the unique key of your feature flag
* @param distinctId the current unique id
* @param matchValue optional- the matched flag string or boolean
* @param options: dict with optional parameters below
* @param onlyEvaluateLocally optional - whether to only evaluate the flag locally. Defaults to false.
*
* @returns payload of a json type object
*/
getFeatureFlagPayload(key: string, distinctId: string, matchValue?: string | boolean, options?: {
onlyEvaluateLocally?: boolean;
}): Promise<JsonType$1 | undefined>;
/**
* @description Sets a groups properties, which allows asking questions like "Who are the most active companies"

@@ -186,2 +232,9 @@ * using my product in PostHog.

}): Promise<string | boolean | undefined>;
getFeatureFlagPayload(key: string, distinctId: string, matchValue?: string | boolean, options?: {
groups?: Record<string, string>;
personProperties?: Record<string, string>;
groupProperties?: Record<string, Record<string, string>>;
onlyEvaluateLocally?: boolean;
sendFeatureFlagEvents?: boolean;
}): Promise<JsonType | undefined>;
isFeatureEnabled(key: string, distinctId: string, options?: {

@@ -200,2 +253,8 @@ groups?: Record<string, string>;

}): Promise<Record<string, string | boolean>>;
getAllFlagsAndPayloads(distinctId: string, options?: {
groups?: Record<string, string>;
personProperties?: Record<string, string>;
groupProperties?: Record<string, Record<string, string>>;
onlyEvaluateLocally?: boolean;
}): Promise<PosthogFlagsAndPayloadsResponse>;
groupIdentify({ groupType, groupKey, properties }: GroupIdentifyMessage): void;

@@ -202,0 +261,0 @@ reloadFeatureFlags(): Promise<void>;

10

lib/posthog-core/src/index.d.ts

@@ -1,2 +0,2 @@

import { PostHogFetchOptions, PostHogFetchResponse, PostHogAutocaptureElement, PostHogDecideResponse, PosthogCoreOptions, PostHogEventProperties, PostHogPersistedProperty } from './types';
import { PostHogFetchOptions, PostHogFetchResponse, PostHogAutocaptureElement, PostHogDecideResponse, PosthogCoreOptions, PostHogEventProperties, PostHogPersistedProperty, JsonType } from './types';
import { RetriableOptions } from './utils';

@@ -82,4 +82,12 @@ export * as utils from './utils';

private setKnownFeatureFlags;
private setKnownFeatureFlagPayloads;
getFeatureFlag(key: string): boolean | string | undefined;
getFeatureFlagPayload(key: string): JsonType | undefined;
getFeatureFlagPayloads(): PostHogDecideResponse['featureFlagPayloads'] | undefined;
_parsePayload(response: any): any;
getFeatureFlags(): PostHogDecideResponse['featureFlags'] | undefined;
getFeatureFlagsAndPayloads(): {
flags: PostHogDecideResponse['featureFlags'] | undefined;
payloads: PostHogDecideResponse['featureFlagPayloads'] | undefined;
};
isFeatureEnabled(key: string): boolean | undefined;

@@ -86,0 +94,0 @@ reloadFeatureFlagsAsync(sendAnonDistinctId?: boolean): Promise<PostHogDecideResponse['featureFlags']>;

@@ -13,2 +13,3 @@ /// <reference types="node" />

featureFlags?: Record<string, boolean | string>;
featureFlagPayloads?: Record<string, JsonType>;
};

@@ -26,2 +27,3 @@ fetchRetryCount?: number;

FeatureFlags = "feature_flags",
FeatureFlagPayloads = "feature_flag_payloads",
OverrideFeatureFlags = "override_feature_flags",

@@ -80,3 +82,14 @@ Queue = "queue",

};
featureFlagPayloads: {
[key: string]: JsonType;
};
errorsWhileComputingFlags: boolean;
sessionRecording: boolean;
};
export declare type PosthogFlagsAndPayloadsResponse = {
featureFlags: PostHogDecideResponse['featureFlags'];
featureFlagPayloads: PostHogDecideResponse['featureFlagPayloads'];
};
export declare type JsonType = string | number | boolean | null | {
[key: string]: JsonType;
} | Array<JsonType>;

7

lib/posthog-node/src/feature-flags.d.ts
/// <reference types="node" />
import { FeatureFlagCondition, PostHogFeatureFlag } from './types';
import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src';
import { JsonType, PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src';
declare class ClientError extends Error {

@@ -23,2 +23,3 @@ constructor(message: string);

featureFlags: Array<PostHogFeatureFlag>;
featureFlagsByKey: Record<string, PostHogFeatureFlag>;
groupTypeMapping: Record<string, string>;

@@ -32,4 +33,6 @@ loadedSuccessfullyOnce: boolean;

getFeatureFlag(key: string, distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>): Promise<string | boolean | undefined>;
getAllFlags(distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>): Promise<{
computeFeatureFlagPayloadLocally(key: string, matchValue: string | boolean): Promise<JsonType | undefined>;
getAllFlagsAndPayloads(distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>): Promise<{
response: Record<string, string | boolean>;
payloads: Record<string, JsonType>;
fallbackToDecide: boolean;

@@ -36,0 +39,0 @@ }>;

@@ -1,2 +0,2 @@

import { PosthogCoreOptions, PostHogFetchOptions, PostHogFetchResponse } from '../../posthog-core/src';
import { JsonType, PosthogCoreOptions, PostHogFetchOptions, PostHogFetchResponse, PosthogFlagsAndPayloadsResponse } from '../../posthog-core/src';
import { EventMessageV1, GroupIdentifyMessage, IdentifyMessageV1, PostHogNodeV1 } from './types';

@@ -33,2 +33,9 @@ export declare type PostHogOptions = PosthogCoreOptions & {

}): Promise<string | boolean | undefined>;
getFeatureFlagPayload(key: string, distinctId: string, matchValue?: string | boolean, options?: {
groups?: Record<string, string>;
personProperties?: Record<string, string>;
groupProperties?: Record<string, Record<string, string>>;
onlyEvaluateLocally?: boolean;
sendFeatureFlagEvents?: boolean;
}): Promise<JsonType | undefined>;
isFeatureEnabled(key: string, distinctId: string, options?: {

@@ -47,2 +54,8 @@ groups?: Record<string, string>;

}): Promise<Record<string, string | boolean>>;
getAllFlagsAndPayloads(distinctId: string, options?: {
groups?: Record<string, string>;
personProperties?: Record<string, string>;
groupProperties?: Record<string, Record<string, string>>;
onlyEvaluateLocally?: boolean;
}): Promise<PosthogFlagsAndPayloadsResponse>;
groupIdentify({ groupType, groupKey, properties }: GroupIdentifyMessage): void;

@@ -49,0 +62,0 @@ reloadFeatureFlags(): Promise<void>;

@@ -0,1 +1,2 @@

import { JsonType } from 'posthog-core/src';
export interface IdentifyMessageV1 {

@@ -38,2 +39,3 @@ distinctId: string;

};
payloads?: Record<string, JsonType>;
};

@@ -130,2 +132,19 @@ deleted: boolean;

/**
* @description Retrieves payload associated with the specified flag and matched value that is passed in.
* (Expected to be used in conjuction with getFeatureFlag but allows for manual lookup).
* If matchValue isn't passed, getFeatureFlag is called implicitly.
* Will try to evaluate for payload locally first otherwise default to network call if allowed
*
* @param key the unique key of your feature flag
* @param distinctId the current unique id
* @param matchValue optional- the matched flag string or boolean
* @param options: dict with optional parameters below
* @param onlyEvaluateLocally optional - whether to only evaluate the flag locally. Defaults to false.
*
* @returns payload of a json type object
*/
getFeatureFlagPayload(key: string, distinctId: string, matchValue?: string | boolean, options?: {
onlyEvaluateLocally?: boolean;
}): Promise<JsonType | undefined>;
/**
* @description Sets a groups properties, which allows asking questions like "Who are the most active companies"

@@ -132,0 +151,0 @@ * using my product in PostHog.

{
"name": "posthog-node",
"version": "2.2.3",
"version": "2.3.0",
"description": "PostHog Node.js integration",

@@ -5,0 +5,0 @@ "repository": "PostHog/posthog-node",

import { createHash } from 'crypto'
import { FeatureFlagCondition, PostHogFeatureFlag } from './types'
import { version } from '../package.json'
import { PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src'
import { JsonType, PostHogFetchOptions, PostHogFetchResponse } from 'posthog-core/src'
import { safeSetTimeout } from 'posthog-core/src/utils'

@@ -47,2 +47,3 @@ import { fetch } from './fetch'

featureFlags: Array<PostHogFeatureFlag>
featureFlagsByKey: Record<string, PostHogFeatureFlag>
groupTypeMapping: Record<string, string>

@@ -66,2 +67,3 @@ loadedSuccessfullyOnce: boolean

this.featureFlags = []
this.featureFlagsByKey = {}
this.groupTypeMapping = {}

@@ -105,6 +107,5 @@ this.loadedSuccessfullyOnce = false

response = this.computeFlagLocally(featureFlag, distinctId, groups, personProperties, groupProperties)
console.debug(`Successfully computed flag locally: ${key} -> ${response}`)
} catch (e) {
if (e instanceof InconclusiveMatchError) {
console.debug(`Can't compute flag locally: ${key}: ${e}`)
console.error(`InconclusiveMatchError when computing flag locally: ${key}: ${e}`)
} else if (e instanceof Error) {

@@ -119,3 +120,26 @@ console.error(`Error computing flag locally: ${key}: ${e}`)

async getAllFlags(
async computeFeatureFlagPayloadLocally(key: string, matchValue: string | boolean): Promise<JsonType | undefined> {
await this.loadFeatureFlags()
let response = undefined
if (!this.loadedSuccessfullyOnce) {
return undefined
}
if (typeof matchValue == 'boolean') {
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue.toString()]
} else if (typeof matchValue == 'string') {
response = this.featureFlagsByKey?.[key]?.filters?.payloads?.[matchValue]
}
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
if (response === undefined) {
return null
}
return response
}
async getAllFlagsAndPayloads(
distinctId: string,

@@ -125,11 +149,21 @@ groups: Record<string, string> = {},

groupProperties: Record<string, Record<string, string>> = {}
): Promise<{ response: Record<string, string | boolean>; fallbackToDecide: boolean }> {
): Promise<{
response: Record<string, string | boolean>
payloads: Record<string, JsonType>
fallbackToDecide: boolean
}> {
await this.loadFeatureFlags()
const response: Record<string, string | boolean> = {}
const payloads: Record<string, JsonType> = {}
let fallbackToDecide = this.featureFlags.length == 0
this.featureFlags.map((flag) => {
this.featureFlags.map(async (flag) => {
try {
response[flag.key] = this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties)
const matchValue = this.computeFlagLocally(flag, distinctId, groups, personProperties, groupProperties)
response[flag.key] = matchValue
const matchPayload = await this.computeFeatureFlagPayloadLocally(flag.key, matchValue)
if (matchPayload) {
payloads[flag.key] = matchPayload
}
} catch (e) {

@@ -145,3 +179,3 @@ if (e instanceof InconclusiveMatchError) {

return { response, fallbackToDecide }
return { response, payloads, fallbackToDecide }
}

@@ -325,2 +359,9 @@

}
if (res && res.status !== 200) {
// something else went wrong, or the server is down.
// In this case, don't override existing flags
return
}
const responseJson = await res.json()

@@ -332,2 +373,6 @@ if (!('flags' in responseJson)) {

this.featureFlags = responseJson.flags || []
this.featureFlagsByKey = this.featureFlags.reduce(
(acc, curr) => ((acc[curr.key] = curr), acc),
<Record<string, PostHogFeatureFlag>>{}
)
this.groupTypeMapping = responseJson.group_type_mapping || {}

@@ -334,0 +379,0 @@ this.loadedSuccessfullyOnce = true

import { version } from '../package.json'
import {
JsonType,
PostHogCore,

@@ -8,2 +9,3 @@ PosthogCoreOptions,

PostHogFetchResponse,
PosthogFlagsAndPayloadsResponse,
PostHogPersistedProperty,

@@ -207,2 +209,69 @@ } from '../../posthog-core/src'

async getFeatureFlagPayload(
key: string,
distinctId: string,
matchValue?: string | boolean,
options?: {
groups?: Record<string, string>
personProperties?: Record<string, string>
groupProperties?: Record<string, Record<string, string>>
onlyEvaluateLocally?: boolean
sendFeatureFlagEvents?: boolean
}
): Promise<JsonType | undefined> {
const { groups, personProperties, groupProperties } = options || {}
let { onlyEvaluateLocally, sendFeatureFlagEvents } = options || {}
let response = undefined
// Try to get match value locally if not provided
if (!matchValue) {
matchValue = await this.getFeatureFlag(key, distinctId, {
...options,
onlyEvaluateLocally: true,
})
}
if (matchValue) {
response = await this.featureFlagsPoller?.computeFeatureFlagPayloadLocally(key, matchValue)
}
// set defaults
if (onlyEvaluateLocally == undefined) {
onlyEvaluateLocally = false
}
if (sendFeatureFlagEvents == undefined) {
sendFeatureFlagEvents = true
}
// set defaults
if (onlyEvaluateLocally == undefined) {
onlyEvaluateLocally = false
}
const payloadWasLocallyEvaluated = response !== undefined
if (!payloadWasLocallyEvaluated && !onlyEvaluateLocally) {
this.reInit(distinctId)
if (groups != undefined) {
this._sharedClient.groups(groups)
}
if (personProperties) {
this._sharedClient.personProperties(personProperties)
}
if (groupProperties) {
this._sharedClient.groupProperties(groupProperties)
}
await this._sharedClient.reloadFeatureFlagsAsync(false)
response = this._sharedClient.getFeatureFlagPayload(key)
}
try {
return JSON.parse(response as any)
} catch {
return response
}
}
async isFeatureEnabled(

@@ -235,2 +304,15 @@ key: string,

): Promise<Record<string, string | boolean>> {
const response = await this.getAllFlagsAndPayloads(distinctId, options)
return response.featureFlags
}
async getAllFlagsAndPayloads(
distinctId: string,
options?: {
groups?: Record<string, string>
personProperties?: Record<string, string>
groupProperties?: Record<string, Record<string, string>>
onlyEvaluateLocally?: boolean
}
): Promise<PosthogFlagsAndPayloadsResponse> {
const { groups, personProperties, groupProperties } = options || {}

@@ -244,3 +326,3 @@ let { onlyEvaluateLocally } = options || {}

const localEvaluationResult = await this.featureFlagsPoller?.getAllFlags(
const localEvaluationResult = await this.featureFlagsPoller?.getAllFlagsAndPayloads(
distinctId,

@@ -252,6 +334,8 @@ groups,

let response = {}
let featureFlags = {}
let featureFlagPayloads = {}
let fallbackToDecide = true
if (localEvaluationResult) {
response = localEvaluationResult.response
featureFlags = localEvaluationResult.response
featureFlagPayloads = localEvaluationResult.payloads
fallbackToDecide = localEvaluationResult.fallbackToDecide

@@ -274,11 +358,18 @@ }

await this._sharedClient.reloadFeatureFlagsAsync(false)
const remoteEvaluationResult = this._sharedClient.getFeatureFlags()
return { ...response, ...remoteEvaluationResult }
const remoteEvaluationResult = this._sharedClient.getFeatureFlagsAndPayloads()
featureFlags = {
...featureFlags,
...(remoteEvaluationResult.flags || {}),
}
featureFlagPayloads = {
...featureFlagPayloads,
...(remoteEvaluationResult.payloads || {}),
}
}
return response
return { featureFlags, featureFlagPayloads }
}
groupIdentify({ groupType, groupKey, properties }: GroupIdentifyMessage): void {
this.reInit(`$${groupType}_${groupKey}`)
this._sharedClient.groupIdentify(groupType, groupKey, properties)

@@ -285,0 +376,0 @@ }

@@ -0,1 +1,3 @@

import { JsonType } from 'posthog-core/src'
export interface IdentifyMessageV1 {

@@ -42,2 +44,3 @@ distinctId: string

}
payloads?: Record<string, JsonType>
}

@@ -145,2 +148,25 @@ deleted: boolean

/**
* @description Retrieves payload associated with the specified flag and matched value that is passed in.
* (Expected to be used in conjuction with getFeatureFlag but allows for manual lookup).
* If matchValue isn't passed, getFeatureFlag is called implicitly.
* Will try to evaluate for payload locally first otherwise default to network call if allowed
*
* @param key the unique key of your feature flag
* @param distinctId the current unique id
* @param matchValue optional- the matched flag string or boolean
* @param options: dict with optional parameters below
* @param onlyEvaluateLocally optional - whether to only evaluate the flag locally. Defaults to false.
*
* @returns payload of a json type object
*/
getFeatureFlagPayload(
key: string,
distinctId: string,
matchValue?: string | boolean,
options?: {
onlyEvaluateLocally?: boolean
}
): Promise<JsonType | undefined>
/**
* @description Sets a groups properties, which allows asking questions like "Who are the most active companies"

@@ -147,0 +173,0 @@ * using my product in PostHog.

@@ -141,2 +141,23 @@ // import { PostHog } from '../'

describe('groupIdentify', () => {
it('should identify group with unique id', () => {
posthog.groupIdentify({ groupType: 'posthog', groupKey: 'team-1', properties: { analytics: true } })
jest.runOnlyPendingTimers()
const batchEvents = getLastBatchEvents()
console.log(batchEvents)
expect(batchEvents).toMatchObject([
{
distinct_id: '$posthog_team-1',
event: '$groupidentify',
properties: {
$group_type: 'posthog',
$group_key: 'team-1',
$group_set: { analytics: true },
$lib: 'posthog-node',
},
},
])
})
})
describe('feature flags', () => {

@@ -150,4 +171,11 @@ beforeEach(() => {

mockedFetch.mockImplementation(apiImplementation({ decideFlags: mockFeatureFlags }))
const mockFeatureFlagPayloads = {
'feature-1': { color: 'blue' },
'feature-variant': 2,
}
mockedFetch.mockImplementation(
apiImplementation({ decideFlags: mockFeatureFlags, decideFlagPayloads: mockFeatureFlagPayloads })
)
posthog = new PostHog('TEST_API_KEY', {

@@ -191,3 +219,3 @@ host: 'http://example.com',

expect(mockedFetch).toHaveBeenCalledWith(
'http://example.com/decide/?v=2',
'http://example.com/decide/?v=3',
expect.objectContaining({ method: 'POST' })

@@ -416,3 +444,19 @@ )

})
it('should do getFeatureFlagPayloads', async () => {
expect(mockedFetch).toHaveBeenCalledTimes(0)
await expect(
posthog.getFeatureFlagPayload('feature-variant', '123', 'variant', { groups: { org: '123' } })
).resolves.toEqual(2)
expect(mockedFetch).toHaveBeenCalledTimes(1)
})
it('should do getFeatureFlagPayloads without matchValue', async () => {
expect(mockedFetch).toHaveBeenCalledTimes(0)
await expect(
posthog.getFeatureFlagPayload('feature-variant', '123', undefined, { groups: { org: '123' } })
).resolves.toEqual(2)
expect(mockedFetch).toHaveBeenCalledTimes(1)
})
})
})

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc