Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

posthog-node

Package Overview
Dependencies
Maintainers
5
Versions
67
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 1.0.10 to 1.0.11

80

cli.js

@@ -8,21 +8,21 @@ #!/usr/bin/env node

const toObject = str => JSON.parse(str)
const toObject = (str) => JSON.parse(str)
program
.version(pkg.version)
.option('-k, --apiKey <key>', 'the PostHog api key to use')
.option('-h, --host <host>', 'the PostHog API hostname to use')
.option('-t, --type <type>', 'the PostHog message type')
.version(pkg.version)
.option('-k, --apiKey <key>', 'the PostHog api key to use')
.option('-h, --host <host>', 'the PostHog API hostname to use')
.option('-t, --type <type>', 'the PostHog message type')
.option('-d, --distinctId <id>', 'the distinct id to send the event as')
.option('-d, --distinctId <id>', 'the distinct id to send the event as')
.option('-e, --event <event>', 'the event name to send with the event')
.option('-p, --properties <properties>', 'the event properties to send (JSON-encoded)', toObject)
.option('-e, --event <event>', 'the event name to send with the event')
.option('-p, --properties <properties>', 'the event properties to send (JSON-encoded)', toObject)
.option('-a, --alias <previousId>', 'alias for the distinct id')
.option('-a, --alias <previousId>', 'alias for the distinct id')
.parse(process.argv)
.parse(process.argv)
if (program.args.length !== 0) {
program.help()
program.help()
}

@@ -41,34 +41,34 @@

const run = (method, args) => {
const posthog = new PostHog(apiKey, { host, flushAt: 1 })
posthog[method](args, err => {
if (err) {
console.error(err.stack)
process.exit(1)
}
})
const posthog = new PostHog(apiKey, { host, flushAt: 1 })
posthog[method](args, (err) => {
if (err) {
console.error(err.stack)
process.exit(1)
}
})
}
switch (type) {
case 'capture':
run('capture', {
event,
properties,
distinctId
})
break
case 'identify':
run('identify', {
properties,
distinctId
})
break
case 'alias':
run('alias', {
alias,
distinctId
})
break
default:
console.error('invalid type:', type)
process.exit(1)
case 'capture':
run('capture', {
event,
properties,
distinctId,
})
break
case 'identify':
run('identify', {
properties,
distinctId,
})
break
case 'alias':
run('alias', {
alias,
distinctId,
})
break
default:
console.error('invalid type:', type)
process.exit(1)
}

@@ -14,16 +14,16 @@ var type = require('component-type')

function eventValidation (event, type) {
validateGenericEvent(event)
type = type || event.type
assert(type, 'You must pass an event type.')
switch (type) {
case 'capture':
return validateCaptureEvent(event)
case 'identify':
return validateIdentifyEvent(event)
case 'alias':
return validateAliasEvent(event)
default:
assert(0, 'Invalid event type: "' + type + '"')
}
function eventValidation(event, type) {
validateGenericEvent(event)
type = type || event.type
assert(type, 'You must pass an event type.')
switch (type) {
case 'capture':
return validateCaptureEvent(event)
case 'identify':
return validateIdentifyEvent(event)
case 'alias':
return validateAliasEvent(event)
default:
assert(0, 'Invalid event type: "' + type + '"')
}
}

@@ -35,5 +35,5 @@

function validateCaptureEvent (event) {
assert(event.distinctId, 'You must pass a "distinctId".')
assert(event.event, 'You must pass an "event".')
function validateCaptureEvent(event) {
assert(event.distinctId, 'You must pass a "distinctId".')
assert(event.event, 'You must pass an "event".')
}

@@ -45,4 +45,4 @@

function validateIdentifyEvent (event) {
assert(event.distinctId, 'You must pass a "distinctId".')
function validateIdentifyEvent(event) {
assert(event.distinctId, 'You must pass a "distinctId".')
}

@@ -54,5 +54,5 @@

function validateAliasEvent (event) {
assert(event.distinctId, 'You must pass a "distinctId".')
assert(event.alias, 'You must pass a "alias".')
function validateAliasEvent(event) {
assert(event.distinctId, 'You must pass a "distinctId".')
assert(event.alias, 'You must pass a "alias".')
}

@@ -65,8 +65,8 @@

var genericValidationRules = {
event: 'string',
properties: 'object',
alias: 'string',
timestamp: 'date',
distinctId: 'string',
type: 'string'
event: 'string',
properties: 'object',
alias: 'string',
timestamp: 'date',
distinctId: 'string',
type: 'string',
}

@@ -78,21 +78,23 @@

function validateGenericEvent (event) {
assert(type(event) === 'object', 'You must pass a message object.')
var json = JSON.stringify(event)
// Strings are variable byte encoded, so json.length is not sufficient.
assert(Buffer.byteLength(json, 'utf8') < MAX_SIZE, 'Your message must be < 32kb.')
function validateGenericEvent(event) {
assert(type(event) === 'object', 'You must pass a message object.')
var json = JSON.stringify(event)
// Strings are variable byte encoded, so json.length is not sufficient.
assert(Buffer.byteLength(json, 'utf8') < MAX_SIZE, 'Your message must be < 32kb.')
for (var key in genericValidationRules) {
var val = event[key]
if (!val) continue
var rule = genericValidationRules[key]
if (type(rule) !== 'array') {
rule = [ rule ]
for (var key in genericValidationRules) {
var val = event[key]
if (!val) continue
var rule = genericValidationRules[key]
if (type(rule) !== 'array') {
rule = [rule]
}
var a = rule[0] === 'object' ? 'an' : 'a'
assert(
rule.some(function (e) {
return type(val) === e
}),
'"' + key + '" must be ' + a + ' ' + join(rule, 'or') + '.'
)
}
var a = rule[0] === 'object' ? 'an' : 'a'
assert(
rule.some(function (e) { return type(val) === e }),
'"' + key + '" must be ' + a + ' ' + join(rule, 'or') + '.'
)
}
}
// Type definitions for posthog-node
// Project: Posthog
declare module "posthog-node" {
interface Option {
flushAt?: number;
flushInterval?: number;
host?: string;
api_host?: string;
enable?: boolean;
}
declare module 'posthog-node' {
interface Option {
flushAt?: number
flushInterval?: number
host?: string
api_host?: string
enable?: boolean
}
interface CommonParamsInterfacePropertiesProp {
[key: string]:
| string
| number
| Array<any | { [key: string]: string | number }>;
}
interface CommonParamsInterfacePropertiesProp {
[key: string]: string | number | Array<any | { [key: string]: string | number }>
}
interface IdentifyMessage {
distinctId: string;
properties?: CommonParamsInterfacePropertiesProp;
}
interface IdentifyMessage {
distinctId: string
properties?: CommonParamsInterfacePropertiesProp
}
interface EventMessage extends IdentifyMessage {
event: string;
}
interface EventMessage extends IdentifyMessage {
event: string
}
export default class PostHog {
constructor(apiKey: string, options?: Option);
/**
* @description Capture allows you to capture anything a user does within your system,
* which you can later use in PostHog to find patterns in usage,
* work out which features to improve or where people are giving up.
* A capture call requires:
* @param distinctId which uniquely identifies your user
* @param event We recommend using [verb] [noun], like movie played or movie updated to easily identify what your events mean later on.
* @param properties OPTIONAL | which can be a dict with any information you'd like to add
*/
capture({ distinctId, event, properties }: EventMessage): PostHog;
export default class PostHog {
constructor(apiKey: string, options?: Option)
/**
* @description Capture allows you to capture anything a user does within your system,
* which you can later use in PostHog to find patterns in usage,
* work out which features to improve or where people are giving up.
* A capture call requires:
* @param distinctId which uniquely identifies your user
* @param event We recommend using [verb] [noun], like movie played or movie updated to easily identify what your events mean later on.
* @param properties OPTIONAL | which can be a dict with any information you'd like to add
*/
capture({ distinctId, event, properties }: EventMessage): PostHog
/**
* @description Identify lets you add metadata on your users so you can more easily identify who they are in PostHog,
* and even do things like segment users by these properties.
* An identify call requires:
* @param distinctId which uniquely identifies your user
* @param properties with a dict with any key: value pairs
*/
identify({ distinctId, properties }: IdentifyMessage): PostHog;
/**
* @description Identify lets you add metadata on your users so you can more easily identify who they are in PostHog,
* and even do things like segment users by these properties.
* An identify call requires:
* @param distinctId which uniquely identifies your user
* @param properties with a dict with any key: value pairs
*/
identify({ distinctId, properties }: IdentifyMessage): PostHog
/**
* @description To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call.
* This will allow you to answer questions like "Which marketing channels leads to users churning after a month?"
* or "What do users do on our website before signing up?"
* In a purely back-end implementation, this means whenever an anonymous user does something, you'll want to send a session ID with the capture call.
* Then, when that users signs up, you want to do an alias call with the session ID and the newly created user ID.
* The same concept applies for when a user logs in. If you're using PostHog in the front-end and back-end,
* doing the identify call in the frontend will be enough.:
* @param distinctId the current unique id
* @param alias the unique ID of the user before
*/
alias(data: { distinctId: string; alias: string }): PostHog;
}
/**
* @description To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call.
* This will allow you to answer questions like "Which marketing channels leads to users churning after a month?"
* or "What do users do on our website before signing up?"
* In a purely back-end implementation, this means whenever an anonymous user does something, you'll want to send a session ID with the capture call.
* Then, when that users signs up, you want to do an alias call with the session ID and the newly created user ID.
* The same concept applies for when a user logs in. If you're using PostHog in the front-end and back-end,
* doing the identify call in the frontend will be enough.:
* @param distinctId the current unique id
* @param alias the unique ID of the user before
*/
alias(data: { distinctId: string; alias: string }): PostHog
}
}

@@ -15,268 +15,271 @@ 'use strict'

class PostHog {
/**
* Initialize a new `PostHog` with your PostHog project's `apiKey` and an
* optional dictionary of `options`.
*
* @param {String} apiKey
* @param {Object} [options] (optional)
* @property {Number} flushAt (default: 20)
* @property {Number} flushInterval (default: 10000)
* @property {String} host (default: 'https://app.posthog.com')
* @property {Boolean} enable (default: true)
*/
/**
* Initialize a new `PostHog` with your PostHog project's `apiKey` and an
* optional dictionary of `options`.
*
* @param {String} apiKey
* @param {Object} [options] (optional)
* @property {Number} flushAt (default: 20)
* @property {Number} flushInterval (default: 10000)
* @property {String} host (default: 'https://app.posthog.com')
* @property {Boolean} enable (default: true)
*/
constructor (apiKey, options) {
options = options || {}
constructor(apiKey, options) {
options = options || {}
assert(apiKey, 'You must pass your PostHog project\'s api key.')
assert(apiKey, "You must pass your PostHog project's api key.")
this.queue = []
this.apiKey = apiKey
this.host = removeSlash(options.host || 'https://app.posthog.com')
this.timeout = options.timeout || false
this.flushAt = Math.max(options.flushAt, 1) || 20
this.flushInterval = typeof options.flushInterval === 'number' ? options.flushInterval : 10000
this.flushed = false
Object.defineProperty(this, 'enable', {
configurable: false,
writable: false,
enumerable: true,
value: typeof options.enable === 'boolean' ? options.enable : true
})
this.queue = []
this.apiKey = apiKey
this.host = removeSlash(options.host || 'https://app.posthog.com')
this.timeout = options.timeout || false
this.flushAt = Math.max(options.flushAt, 1) || 20
this.flushInterval = typeof options.flushInterval === 'number' ? options.flushInterval : 10000
this.flushed = false
Object.defineProperty(this, 'enable', {
configurable: false,
writable: false,
enumerable: true,
value: typeof options.enable === 'boolean' ? options.enable : true,
})
axiosRetry(axios, {
retries: options.retryCount || 3,
retryCondition: this._isErrorRetryable,
retryDelay: axiosRetry.exponentialDelay
})
}
axiosRetry(axios, {
retries: options.retryCount || 3,
retryCondition: this._isErrorRetryable,
retryDelay: axiosRetry.exponentialDelay,
})
}
_validate (message, type) {
try {
looselyValidate(message, type)
} catch (e) {
if (e.message === 'Your message must be < 32kb.') {
console.log('Your message must be < 32kb. This is currently surfaced as a warning to allow clients to update. Versions released after August 1, 2018 will throw an error instead. Please update your code before then.', message)
return
}
throw e
_validate(message, type) {
try {
looselyValidate(message, type)
} catch (e) {
if (e.message === 'Your message must be < 32kb.') {
console.log(
'Your message must be < 32kb. This is currently surfaced as a warning to allow clients to update. Versions released after August 1, 2018 will throw an error instead. Please update your code before then.',
message
)
return
}
throw e
}
}
}
/**
* Send an identify `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
/**
* Send an identify `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
identify (message, callback) {
this._validate(message, 'identify')
identify(message, callback) {
this._validate(message, 'identify')
const apiMessage = Object.assign({}, message, {
'$set': message.properties || {},
event: '$identify',
properties: {
'$lib': 'posthog-node',
'$lib_version': version
}
})
const apiMessage = Object.assign({}, message, {
$set: message.properties || {},
event: '$identify',
properties: {
$lib: 'posthog-node',
$lib_version: version,
},
})
this.enqueue('identify', apiMessage, callback)
return this
}
this.enqueue('identify', apiMessage, callback)
return this
}
/**
* Send a capture `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
/**
* Send a capture `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
capture (message, callback) {
this._validate(message, 'capture')
capture(message, callback) {
this._validate(message, 'capture')
const apiMessage = Object.assign({}, message, {
properties: Object.assign({}, message.properties, {
'$lib': 'posthog-node',
'$lib_version': version
})
})
const apiMessage = Object.assign({}, message, {
properties: Object.assign({}, message.properties, {
$lib: 'posthog-node',
$lib_version: version,
}),
})
this.enqueue('capture', apiMessage, callback)
return this
}
this.enqueue('capture', apiMessage, callback)
return this
}
/**
* Send an alias `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
/**
* Send an alias `message`.
*
* @param {Object} message
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
alias (message, callback) {
this._validate(message, 'alias')
alias(message, callback) {
this._validate(message, 'alias')
const apiMessage = Object.assign({}, message, {
event: '$create_alias',
properties: {
distinct_id: message.distinctId || message.distinct_id,
alias: message.alias,
'$lib': 'posthog-node',
'$lib_version': version
}
})
delete apiMessage.alias
delete apiMessage.distinctId
apiMessage.distinct_id = null
const apiMessage = Object.assign({}, message, {
event: '$create_alias',
properties: {
distinct_id: message.distinctId || message.distinct_id,
alias: message.alias,
$lib: 'posthog-node',
$lib_version: version,
},
})
delete apiMessage.alias
delete apiMessage.distinctId
apiMessage.distinct_id = null
this.enqueue('alias', apiMessage, callback)
return this
}
this.enqueue('alias', apiMessage, callback)
return this
}
/**
* Add a `message` of type `type` to the queue and
* check whether it should be flushed.
*
* @param {String} type
* @param {Object} message
* @param {Function} [callback] (optional)
* @api private
*/
/**
* Add a `message` of type `type` to the queue and
* check whether it should be flushed.
*
* @param {String} type
* @param {Object} message
* @param {Function} [callback] (optional)
* @api private
*/
enqueue (type, message, callback) {
callback = callback || noop
enqueue(type, message, callback) {
callback = callback || noop
if (!this.enable) {
return setImmediate(callback)
}
if (!this.enable) {
return setImmediate(callback)
}
message = Object.assign({}, message)
message.type = type
message.library = 'posthog-node'
message.library_version = version
message = Object.assign({}, message)
message.type = type
message.library = 'posthog-node'
message.library_version = version
if (!message.timestamp) {
message.timestamp = new Date()
}
if (!message.timestamp) {
message.timestamp = new Date()
}
if (message.distinctId) {
message.distinct_id = message.distinctId
delete message.distinctId
}
if (message.distinctId) {
message.distinct_id = message.distinctId
delete message.distinctId
}
this.queue.push({ message, callback })
this.queue.push({ message, callback })
if (!this.flushed) {
this.flushed = true
this.flush()
return
}
if (!this.flushed) {
this.flushed = true
this.flush()
return
}
if (this.queue.length >= this.flushAt) {
this.flush()
}
if (this.queue.length >= this.flushAt) {
this.flush()
}
if (this.flushInterval && !this.timer) {
this.timer = setTimeout(this.flush.bind(this), this.flushInterval)
if (this.flushInterval && !this.timer) {
this.timer = setTimeout(this.flush.bind(this), this.flushInterval)
}
}
}
/**
* Flush the current queue
*
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
/**
* Flush the current queue
*
* @param {Function} [callback] (optional)
* @return {PostHog}
*/
flush (callback) {
callback = callback || noop
flush(callback) {
callback = callback || noop
if (!this.enable) {
return setImmediate(callback)
}
if (!this.enable) {
return setImmediate(callback)
}
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
if (!this.queue.length) {
return setImmediate(callback)
}
if (!this.queue.length) {
return setImmediate(callback)
}
const items = this.queue.splice(0, this.flushAt)
const callbacks = items.map(item => item.callback)
const messages = items.map(item => item.message)
const items = this.queue.splice(0, this.flushAt)
const callbacks = items.map((item) => item.callback)
const messages = items.map((item) => item.message)
const data = {
api_key: this.apiKey,
batch: messages
}
const data = {
api_key: this.apiKey,
batch: messages,
}
const done = err => {
callbacks.forEach(callback => callback(err))
callback(err, data)
}
const done = (err) => {
callbacks.forEach((callback) => callback(err))
callback(err, data)
}
// Don't set the user agent if we're not on a browser. The latest spec allows
// the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
// and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
// but browsers such as Chrome and Safari have not caught up.
const headers = {}
if (typeof window === 'undefined') {
headers['user-agent'] = `posthog-node/${version}`
}
// Don't set the user agent if we're not on a browser. The latest spec allows
// the User-Agent header (see https://fetch.spec.whatwg.org/#terminology-headers
// and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader),
// but browsers such as Chrome and Safari have not caught up.
const headers = {}
if (typeof window === 'undefined') {
headers['user-agent'] = `posthog-node/${version}`
}
const req = {
method: 'POST',
url: `${this.host}/batch/`,
data,
headers
}
const req = {
method: 'POST',
url: `${this.host}/batch/`,
data,
headers,
}
if (this.timeout) {
req.timeout = typeof this.timeout === 'string' ? ms(this.timeout) : this.timeout
if (this.timeout) {
req.timeout = typeof this.timeout === 'string' ? ms(this.timeout) : this.timeout
}
axios(req)
.then(() => done())
.catch((err) => {
if (err.response) {
const error = new Error(err.response.statusText)
return done(error)
}
done(err)
})
}
axios(req)
.then(() => done())
.catch(err => {
if (err.response) {
const error = new Error(err.response.statusText)
return done(error)
_isErrorRetryable(error) {
// Retry Network Errors.
if (axiosRetry.isNetworkError(error)) {
return true
}
done(err)
})
}
if (!error.response) {
// Cannot determine if the request can be retried
return false
}
_isErrorRetryable (error) {
// Retry Network Errors.
if (axiosRetry.isNetworkError(error)) {
return true
}
// Retry Server Errors (5xx).
if (error.response.status >= 500 && error.response.status <= 599) {
return true
}
if (!error.response) {
// Cannot determine if the request can be retried
return false
}
// Retry if rate limited.
if (error.response.status === 429) {
return true
}
// Retry Server Errors (5xx).
if (error.response.status >= 500 && error.response.status <= 599) {
return true
return false
}
// Retry if rate limited.
if (error.response.status === 429) {
return true
}
return false
}
}
module.exports = PostHog
{
"name": "posthog-node",
"version": "1.0.10",
"description": "PostHog Node.js integration",
"license": "MIT",
"repository": "PostHog/posthog-node",
"author": {
"name": "PostHog",
"email": "hey@posthog.com",
"url": "https://posthog.com"
},
"engines": {
"node": ">=4"
},
"size-limit": [
{
"limit": "25 KB",
"path": "index.js"
"name": "posthog-node",
"version": "1.0.11",
"description": "PostHog Node.js integration",
"license": "MIT",
"repository": "PostHog/posthog-node",
"author": {
"name": "PostHog",
"email": "hey@posthog.com",
"url": "https://posthog.com"
},
"engines": {
"node": ">=4"
},
"size-limit": [
{
"limit": "25 KB",
"path": "index.js"
}
],
"scripts": {
"dependencies": "yarn",
"size": "size-limit",
"test": "nyc ava",
"report-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov",
"format": "prettier --write ."
},
"files": [
"index.js",
"index.d.ts",
"event-validation.js",
"cli.js"
],
"bin": {
"posthog": "cli.js"
},
"keywords": [
"posthog",
"stats",
"analysis",
"funnels"
],
"dependencies": {
"axios": "^0.21.1",
"axios-retry": "^3.1.9",
"component-type": "^1.2.1",
"join-component": "^1.1.0",
"md5": "^2.3.0",
"ms": "^2.1.3",
"remove-trailing-slash": "^0.1.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"ava": "^0.25.0",
"basic-auth": "^2.0.1",
"body-parser": "^1.17.1",
"codecov": "^3.0.0",
"commander": "^2.9.0",
"delay": "^4.2.0",
"express": "^4.15.2",
"nyc": "^14.1.1",
"pify": "^4.0.1",
"prettier": "^2.3.1",
"sinon": "^7.3.2",
"size-limit": "^1.3.5",
"snyk": "^1.171.1"
}
],
"scripts": {
"dependencies": "yarn",
"size": "size-limit",
"test": "standard && nyc ava",
"report-coverage": "nyc report --reporter=lcov > coverage.lcov && codecov"
},
"files": [
"index.js",
"index.d.ts",
"event-validation.js",
"cli.js"
],
"bin": {
"posthog": "cli.js"
},
"keywords": [
"posthog",
"stats",
"analysis",
"funnels"
],
"dependencies": {
"axios": "^0.21.1",
"axios-retry": "^3.1.9",
"component-type": "^1.2.1",
"join-component": "^1.1.0",
"md5": "^2.3.0",
"ms": "^2.1.3",
"remove-trailing-slash": "^0.1.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"ava": "^0.25.0",
"basic-auth": "^2.0.1",
"body-parser": "^1.17.1",
"codecov": "^3.0.0",
"commander": "^2.9.0",
"delay": "^4.2.0",
"express": "^4.15.2",
"nyc": "^14.1.1",
"pify": "^4.0.1",
"sinon": "^7.3.2",
"size-limit": "^1.3.5",
"snyk": "^1.171.1",
"standard": "^12.0.1"
}
}

@@ -10,1 +10,11 @@ # PostHog NodeJS

### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ)
## Development
### How to Release
1. Run `npm version patch` (or minor/major)
3. Run `npm publish`
## Thank You
This library is largely based on the `analytics-node` package.
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