Socket
Socket
Sign inDemoInstall

node-html-parser

Package Overview
Dependencies
Maintainers
1
Versions
119
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-html-parser - npm Package Compare versions

Comparing version 1.1.6 to 1.1.7

2

dist/index.d.ts
export declare enum NodeType {
ELEMENT_NODE = 1,
TEXT_NODE = 3,
TEXT_NODE = 3
}

@@ -5,0 +5,0 @@ /**

@@ -0,0 +0,0 @@ "use strict";

export declare enum NodeType {
ELEMENT_NODE = 1,
TEXT_NODE = 3,
TEXT_NODE = 3
}

@@ -5,0 +5,0 @@ /**

@@ -0,1 +1,14 @@

var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
(function (factory) {

@@ -12,3 +25,3 @@ if (typeof module === "object" && typeof module.exports === "object") {

Object.defineProperty(exports, "__esModule", { value: true });
const he_1 = require("he");
var he_1 = require("he");
var NodeType;

@@ -22,7 +35,8 @@ (function (NodeType) {

*/
class Node {
constructor() {
var Node = /** @class */ (function () {
function Node() {
this.childNodes = [];
}
}
return Node;
}());
exports.Node = Node;

@@ -33,5 +47,6 @@ /**

*/
class TextNode extends Node {
constructor(value) {
super();
var TextNode = /** @class */ (function (_super) {
__extends(TextNode, _super);
function TextNode(value) {
var _this = _super.call(this) || this;
/**

@@ -41,25 +56,35 @@ * Node Type declaration.

*/
this.nodeType = NodeType.TEXT_NODE;
this.rawText = value;
_this.nodeType = NodeType.TEXT_NODE;
_this.rawText = value;
return _this;
}
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get text() {
return he_1.decode(this.rawText);
}
/**
* Detect if the node contains only white space.
* @return {bool}
*/
get isWhitespace() {
return /^(\s| )*$/.test(this.rawText);
}
toString() {
Object.defineProperty(TextNode.prototype, "text", {
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get: function () {
return he_1.decode(this.rawText);
},
enumerable: true,
configurable: true
});
Object.defineProperty(TextNode.prototype, "isWhitespace", {
/**
* Detect if the node contains only white space.
* @return {bool}
*/
get: function () {
return /^(\s| )*$/.test(this.rawText);
},
enumerable: true,
configurable: true
});
TextNode.prototype.toString = function () {
return this.text;
}
}
};
return TextNode;
}(Node));
exports.TextNode = TextNode;
const kBlockElements = {
var kBlockElements = {
div: true,

@@ -88,3 +113,4 @@ p: true,

*/
class HTMLElement extends Node {
var HTMLElement = /** @class */ (function (_super) {
__extends(HTMLElement, _super);
/**

@@ -98,5 +124,5 @@ * Creates an instance of HTMLElement.

*/
constructor(name, keyAttrs, rawAttrs) {
super();
this.classNames = [];
function HTMLElement(name, keyAttrs, rawAttrs) {
var _this = _super.call(this) || this;
_this.classNames = [];
/**

@@ -106,90 +132,103 @@ * Node Type declaration.

*/
this.nodeType = NodeType.ELEMENT_NODE;
this.tagName = name;
this.rawAttrs = rawAttrs || '';
_this.nodeType = NodeType.ELEMENT_NODE;
_this.tagName = name;
_this.rawAttrs = rawAttrs || '';
// this.parentNode = null;
this.childNodes = [];
_this.childNodes = [];
if (keyAttrs.id) {
this.id = keyAttrs.id;
_this.id = keyAttrs.id;
}
if (keyAttrs.class) {
this.classNames = keyAttrs.class.split(/\s+/);
_this.classNames = keyAttrs.class.split(/\s+/);
}
return _this;
}
/**
* Get escpaed (as-it) text value of current node and its children.
* @return {string} text content
*/
get rawText() {
let res = '';
for (let i = 0; i < this.childNodes.length; i++)
res += this.childNodes[i].rawText;
return res;
}
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get text() {
return he_1.decode(this.rawText);
}
/**
* Get structured Text (with '\n' etc.)
* @return {string} structured text
*/
get structuredText() {
let currentBlock = [];
const blocks = [currentBlock];
function dfs(node) {
if (node.nodeType === NodeType.ELEMENT_NODE) {
if (kBlockElements[node.tagName]) {
if (currentBlock.length > 0) {
blocks.push(currentBlock = []);
Object.defineProperty(HTMLElement.prototype, "rawText", {
/**
* Get escpaed (as-it) text value of current node and its children.
* @return {string} text content
*/
get: function () {
var res = '';
for (var i = 0; i < this.childNodes.length; i++)
res += this.childNodes[i].rawText;
return res;
},
enumerable: true,
configurable: true
});
Object.defineProperty(HTMLElement.prototype, "text", {
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get: function () {
return he_1.decode(this.rawText);
},
enumerable: true,
configurable: true
});
Object.defineProperty(HTMLElement.prototype, "structuredText", {
/**
* Get structured Text (with '\n' etc.)
* @return {string} structured text
*/
get: function () {
var currentBlock = [];
var blocks = [currentBlock];
function dfs(node) {
if (node.nodeType === NodeType.ELEMENT_NODE) {
if (kBlockElements[node.tagName]) {
if (currentBlock.length > 0) {
blocks.push(currentBlock = []);
}
node.childNodes.forEach(dfs);
if (currentBlock.length > 0) {
blocks.push(currentBlock = []);
}
}
node.childNodes.forEach(dfs);
if (currentBlock.length > 0) {
blocks.push(currentBlock = []);
else {
node.childNodes.forEach(dfs);
}
}
else {
node.childNodes.forEach(dfs);
}
}
else if (node.nodeType === NodeType.TEXT_NODE) {
if (node.isWhitespace) {
// Whitespace node, postponed output
currentBlock.prependWhitespace = true;
}
else {
let text = node.text;
if (currentBlock.prependWhitespace) {
text = ' ' + text;
currentBlock.prependWhitespace = false;
else if (node.nodeType === NodeType.TEXT_NODE) {
if (node.isWhitespace) {
// Whitespace node, postponed output
currentBlock.prependWhitespace = true;
}
currentBlock.push(text);
else {
var text = node.text;
if (currentBlock.prependWhitespace) {
text = ' ' + text;
currentBlock.prependWhitespace = false;
}
currentBlock.push(text);
}
}
}
}
dfs(this);
return blocks
.map(function (block) {
// Normalize each line's whitespace
return block.join('').trim().replace(/\s{2,}/g, ' ');
})
.join('\n').replace(/\s+$/, ''); // trimRight;
}
toString() {
const tag = this.tagName;
dfs(this);
return blocks
.map(function (block) {
// Normalize each line's whitespace
return block.join('').trim().replace(/\s{2,}/g, ' ');
})
.join('\n').replace(/\s+$/, ''); // trimRight;
},
enumerable: true,
configurable: true
});
HTMLElement.prototype.toString = function () {
var tag = this.tagName;
if (tag) {
const is_un_closed = /^meta$/i.test(tag);
const is_self_closed = /^(img|br|hr|area|base|input|doctype|link)$/i.test(tag);
const attrs = this.rawAttrs ? ' ' + this.rawAttrs : '';
var is_un_closed = /^meta$/i.test(tag);
var is_self_closed = /^(img|br|hr|area|base|input|doctype|link)$/i.test(tag);
var attrs = this.rawAttrs ? ' ' + this.rawAttrs : '';
if (is_un_closed) {
return `<${tag}${attrs}>`;
return "<" + tag + attrs + ">";
}
else if (is_self_closed) {
return `<${tag}${attrs} />`;
return "<" + tag + attrs + " />";
}
else {
return `<${tag}${attrs}>${this.innerHTML}</${tag}>`;
return "<" + tag + attrs + ">" + this.innerHTML + "</" + tag + ">";
}

@@ -200,9 +239,13 @@ }

}
}
get innerHTML() {
return this.childNodes.map((child) => {
return child.toString();
}).join('');
}
set_content(content) {
};
Object.defineProperty(HTMLElement.prototype, "innerHTML", {
get: function () {
return this.childNodes.map(function (child) {
return child.toString();
}).join('');
},
enumerable: true,
configurable: true
});
HTMLElement.prototype.set_content = function (content) {
if (content instanceof Node) {

@@ -212,10 +255,14 @@ content = [content];

else if (typeof content == 'string') {
const r = parse(content);
var r = parse(content);
content = r.childNodes.length ? r.childNodes : [new TextNode(content)];
}
this.childNodes = content;
}
get outerHTML() {
return this.toString();
}
};
Object.defineProperty(HTMLElement.prototype, "outerHTML", {
get: function () {
return this.toString();
},
enumerable: true,
configurable: true
});
/**

@@ -226,6 +273,6 @@ * Trim element from right (in block) after seeing pattern in a TextNode.

*/
trimRight(pattern) {
HTMLElement.prototype.trimRight = function (pattern) {
function dfs(node) {
for (let i = 0; i < node.childNodes.length; i++) {
const childNode = node.childNodes[i];
for (var i = 0; i < node.childNodes.length; i++) {
var childNode = node.childNodes[i];
if (childNode.nodeType === NodeType.ELEMENT_NODE) {

@@ -235,3 +282,3 @@ dfs(childNode);

else {
const index = childNode.rawText.search(pattern);
var index = childNode.rawText.search(pattern);
if (index > -1) {

@@ -247,33 +294,37 @@ childNode.rawText = childNode.rawText.substr(0, index);

return this;
}
/**
* Get DOM structure
* @return {string} strucutre
*/
get structure() {
const res = [];
let indention = 0;
function write(str) {
res.push(' '.repeat(indention) + str);
}
function dfs(node) {
const idStr = node.id ? ('#' + node.id) : '';
const classStr = node.classNames.length ? ('.' + node.classNames.join('.')) : '';
write(node.tagName + idStr + classStr);
indention++;
for (let i = 0; i < node.childNodes.length; i++) {
const childNode = node.childNodes[i];
if (childNode.nodeType === NodeType.ELEMENT_NODE) {
dfs(childNode);
};
Object.defineProperty(HTMLElement.prototype, "structure", {
/**
* Get DOM structure
* @return {string} strucutre
*/
get: function () {
var res = [];
var indention = 0;
function write(str) {
res.push(' '.repeat(indention) + str);
}
function dfs(node) {
var idStr = node.id ? ('#' + node.id) : '';
var classStr = node.classNames.length ? ('.' + node.classNames.join('.')) : '';
write(node.tagName + idStr + classStr);
indention++;
for (var i = 0; i < node.childNodes.length; i++) {
var childNode = node.childNodes[i];
if (childNode.nodeType === NodeType.ELEMENT_NODE) {
dfs(childNode);
}
else if (childNode.nodeType === NodeType.TEXT_NODE) {
if (!childNode.isWhitespace)
write('#text');
}
}
else if (childNode.nodeType === NodeType.TEXT_NODE) {
if (!childNode.isWhitespace)
write('#text');
}
indention--;
}
indention--;
}
dfs(this);
return res.join('\n');
}
dfs(this);
return res.join('\n');
},
enumerable: true,
configurable: true
});
/**

@@ -283,6 +334,6 @@ * Remove whitespaces in this sub tree.

*/
removeWhitespace() {
let o = 0;
for (let i = 0; i < this.childNodes.length; i++) {
const node = this.childNodes[i];
HTMLElement.prototype.removeWhitespace = function () {
var o = 0;
for (var i = 0; i < this.childNodes.length; i++) {
var node = this.childNodes[i];
if (node.nodeType === NodeType.TEXT_NODE) {

@@ -300,3 +351,3 @@ if (node.isWhitespace)

return this;
}
};
/**

@@ -308,4 +359,4 @@ * Query CSS selector to find matching nodes.

*/
querySelectorAll(selector) {
let matcher;
HTMLElement.prototype.querySelectorAll = function (selector) {
var matcher;
if (selector instanceof Matcher) {

@@ -318,9 +369,9 @@ matcher = selector;

}
const res = [];
const stack = [];
for (let i = 0; i < this.childNodes.length; i++) {
var res = [];
var stack = [];
for (var i = 0; i < this.childNodes.length; i++) {
stack.push([this.childNodes[i], 0, false]);
while (stack.length) {
const state = arr_back(stack);
const el = state[0];
var state = arr_back(stack);
var el = state[0];
if (state[1] === 0) {

@@ -353,3 +404,3 @@ // Seen for first time.

return res;
}
};
/**

@@ -361,4 +412,4 @@ * Query CSS Selector to find matching node.

*/
querySelector(selector) {
let matcher;
HTMLElement.prototype.querySelector = function (selector) {
var matcher;
if (selector instanceof Matcher) {

@@ -371,8 +422,8 @@ matcher = selector;

}
const stack = [];
for (let i = 0; i < this.childNodes.length; i++) {
var stack = [];
for (var i = 0; i < this.childNodes.length; i++) {
stack.push([this.childNodes[i], 0, false]);
while (stack.length) {
const state = arr_back(stack);
const el = state[0];
var state = arr_back(stack);
var el = state[0];
if (state[1] === 0) {

@@ -401,3 +452,3 @@ // Seen for first time.

return null;
}
};
/**

@@ -408,54 +459,71 @@ * Append a child node to childNodes

*/
appendChild(node) {
HTMLElement.prototype.appendChild = function (node) {
// node.parentNode = this;
this.childNodes.push(node);
return node;
}
/**
* Get first child node
* @return {Node} first child node
*/
get firstChild() {
return this.childNodes[0];
}
/**
* Get last child node
* @return {Node} last child node
*/
get lastChild() {
return arr_back(this.childNodes);
}
/**
* Get attributes
* @return {Object} parsed and unescaped attributes
*/
get attributes() {
if (this._attrs)
};
Object.defineProperty(HTMLElement.prototype, "firstChild", {
/**
* Get first child node
* @return {Node} first child node
*/
get: function () {
return this.childNodes[0];
},
enumerable: true,
configurable: true
});
Object.defineProperty(HTMLElement.prototype, "lastChild", {
/**
* Get last child node
* @return {Node} last child node
*/
get: function () {
return arr_back(this.childNodes);
},
enumerable: true,
configurable: true
});
Object.defineProperty(HTMLElement.prototype, "attributes", {
/**
* Get attributes
* @return {Object} parsed and unescaped attributes
*/
get: function () {
if (this._attrs)
return this._attrs;
this._attrs = {};
var attrs = this.rawAttributes;
for (var key in attrs) {
this._attrs[key] = he_1.decode(attrs[key]);
}
return this._attrs;
this._attrs = {};
const attrs = this.rawAttributes;
for (const key in attrs) {
this._attrs[key] = he_1.decode(attrs[key]);
}
return this._attrs;
}
/**
* Get escaped (as-it) attributes
* @return {Object} parsed attributes
*/
get rawAttributes() {
if (this._rawAttrs)
return this._rawAttrs;
const attrs = {};
if (this.rawAttrs) {
const re = /\b([a-z][a-z0-9\-]*)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
let match;
while (match = re.exec(this.rawAttrs)) {
attrs[match[1]] = match[3] || match[4] || match[5];
},
enumerable: true,
configurable: true
});
Object.defineProperty(HTMLElement.prototype, "rawAttributes", {
/**
* Get escaped (as-it) attributes
* @return {Object} parsed attributes
*/
get: function () {
if (this._rawAttrs)
return this._rawAttrs;
var attrs = {};
if (this.rawAttrs) {
var re = /\b([a-z][a-z0-9\-]*)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
var match = void 0;
while (match = re.exec(this.rawAttrs)) {
attrs[match[1]] = match[3] || match[4] || match[5];
}
}
}
this._rawAttrs = attrs;
return attrs;
}
}
this._rawAttrs = attrs;
return attrs;
},
enumerable: true,
configurable: true
});
return HTMLElement;
}(Node));
exports.HTMLElement = HTMLElement;

@@ -466,3 +534,3 @@ /**

*/
let pMatchFunctionCache = {};
var pMatchFunctionCache = {};
/**

@@ -473,3 +541,3 @@ * Matcher class to make CSS match

*/
class Matcher {
var Matcher = /** @class */ (function () {
/**

@@ -481,19 +549,19 @@ * Creates an instance of Matcher.

*/
constructor(selector) {
function Matcher(selector) {
this.nextMatch = 0;
this.matchers = selector.split(' ').map((matcher) => {
this.matchers = selector.split(' ').map(function (matcher) {
if (pMatchFunctionCache[matcher])
return pMatchFunctionCache[matcher];
const parts = matcher.split('.');
const tagName = parts[0];
const classes = parts.slice(1).sort();
let source = '"use strict";';
var parts = matcher.split('.');
var tagName = parts[0];
var classes = parts.slice(1).sort();
var source = '"use strict";';
if (tagName && tagName != '*') {
let matcher;
var matcher_1;
if (tagName[0] == '#') {
source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';
}
else if (matcher = tagName.match(/^\[\s*(\S+)\s*(=|!=)\s*((((["'])([^\6]*)\6))|(\S*?))\]\s*/)) {
const attr_key = matcher[1];
let method = matcher[2];
else if (matcher_1 = tagName.match(/^\[\s*(\S+)\s*(=|!=)\s*((((["'])([^\6]*)\6))|(\S*?))\]\s*/)) {
var attr_key = matcher_1[1];
var method = matcher_1[2];
if (method !== '=' && method !== '!=') {

@@ -505,4 +573,4 @@ throw new Error('Selector not supported, Expect [key${op}value].op must be =,!=');

}
const value = matcher[7] || matcher[8];
source += `const attrs = el.attributes;for (const key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val ${method} "${value}"){return true;}} return false;`;
var value = matcher_1[7] || matcher_1[8];
source += "const attrs = el.attributes;for (const key in attrs){const val = attrs[key]; if (key == \"" + attr_key + "\" && val " + method + " \"" + value + "\"){return true;}} return false;";
}

@@ -525,3 +593,3 @@ else {

*/
advance(el) {
Matcher.prototype.advance = function (el) {
if (this.nextMatch < this.matchers.length &&

@@ -533,34 +601,39 @@ this.matchers[this.nextMatch](el)) {

return false;
}
};
/**
* Rewind the match pointer
*/
rewind() {
Matcher.prototype.rewind = function () {
this.nextMatch--;
}
};
Object.defineProperty(Matcher.prototype, "matched", {
/**
* Trying to determine if match made.
* @return {bool} true when the match is made
*/
get: function () {
return this.nextMatch == this.matchers.length;
},
enumerable: true,
configurable: true
});
/**
* Trying to determine if match made.
* @return {bool} true when the match is made
*/
get matched() {
return this.nextMatch == this.matchers.length;
}
/**
* Rest match pointer.
* @return {[type]} [description]
*/
reset() {
Matcher.prototype.reset = function () {
this.nextMatch = 0;
}
};
/**
* flush cache to free memory
*/
flushCache() {
Matcher.prototype.flushCache = function () {
pMatchFunctionCache = {};
}
}
};
return Matcher;
}());
exports.Matcher = Matcher;
const kMarkupPattern = /<!--[^]*?(?=-->)-->|<(\/?)([a-z][a-z0-9]*-?[a-z0-9]*)\s*([^>]*?)(\/?)>/ig;
const kAttributePattern = /(^|\s)(id|class)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
const kSelfClosingElements = {
var kMarkupPattern = /<!--[^]*?(?=-->)-->|<(\/?)([a-z][a-z0-9]*-?[a-z0-9]*)\s*([^>]*?)(\/?)>/ig;
var kAttributePattern = /(^|\s)(id|class)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
var kSelfClosingElements = {
meta: true,

@@ -574,3 +647,3 @@ img: true,

};
const kElementsClosedByOpening = {
var kElementsClosedByOpening = {
li: { li: true },

@@ -581,3 +654,3 @@ p: { p: true, div: true },

};
const kElementsClosedByClosing = {
var kElementsClosedByClosing = {
li: { ul: true, ol: true },

@@ -591,3 +664,3 @@ a: { div: true },

};
const kBlockTextElements = {
var kBlockTextElements = {
script: true,

@@ -605,8 +678,8 @@ noscript: true,

function parse(data, options) {
const root = new HTMLElement(null, {});
let currentParent = root;
const stack = [root];
let lastTextPos = -1;
var root = new HTMLElement(null, {});
var currentParent = root;
var stack = [root];
var lastTextPos = -1;
options = options || {};
let match;
var match;
while (match = kMarkupPattern.exec(data)) {

@@ -616,3 +689,3 @@ if (lastTextPos > -1) {

// if has content
const text = data.substring(lastTextPos, kMarkupPattern.lastIndex - match[0].length);
var text = data.substring(lastTextPos, kMarkupPattern.lastIndex - match[0].length);
currentParent.appendChild(new TextNode(text));

@@ -647,3 +720,3 @@ }

if (options[match[2]]) {
let text;
var text = void 0;
if (index == -1) {

@@ -650,0 +723,0 @@ // there is no matching ending for the text element.

const gulp = require('gulp');
const shell = require('gulp-shell');
const sequence = require('gulp-sequence');

@@ -8,46 +10,10 @@ gulp.task('clean', () => {

gulp.task('compile-ts', () => {
const ts = require('gulp-typescript');
const tsProject = ts.createProject('./tsconfig.json');
const dest = tsProject.options.outDir;
return tsProject.src()
.pipe(tsProject())
.pipe(gulp.dest(dest));
});
gulp.task('compile-ts', shell.task('tsc -m commonjs'));
gulp.task('compile-ts-umd', () => {
const ts = require('gulp-typescript');
const tsProject = ts.createProject('./tsconfig.json');
const path = require('path');
const dest = path.join(tsProject.options.outDir, 'umd');
tsProject.options.module = 3;
return tsProject.src()
.pipe(tsProject())
.pipe(gulp.dest(dest));
});
gulp.task('compile-ts-umd', shell.task('tsc -t es5 -m umd --outDir ./dist/umd/'));
gulp.task('watch-ts', async () => {
const ts = require('gulp-typescript');
const tsProject = ts.createProject('./tsconfig.json');
const path = require('path');
const dest = tsProject.options.outDir;
await tsProject.src()
.pipe(tsProject())
.pipe(gulp.dest(dest));
return gulp.watch(['./src/**/*.ts'], (file) => {
const tsProject = ts.createProject('./tsconfig.json');
const relative = path.relative('./', path.dirname(file.path));
const outDir = tsProject.options.outDir;
const dest = path.join(outDir, relative);
return gulp.src(file.path)
.pipe(tsProject())
.pipe(gulp.dest(dest));
});
});
gulp.task('watch-ts', shell.task('tsc -w -t es5 -m umd --outDir ./dist/umd/'));
gulp.task('default', (cb) => {
const sequence = require('gulp-sequence');
sequence('clean', 'compile-ts', 'compile-ts-umd', cb);
});
gulp.task('default', sequence('clean', 'compile-ts', 'compile-ts-umd'));
gulp.task('dev', ['watch-ts']);
{
"name": "node-html-parser",
"version": "1.1.6",
"version": "1.1.7",
"description": "A very fast HTML parser, generating a simplified DOM, with basic element query support.",

@@ -32,3 +32,3 @@ "main": "dist/index.js",

"gulp-sequence": "latest",
"gulp-typescript": "latest",
"gulp-shell": "^0.6.5",
"mocha": "latest",

@@ -35,0 +35,0 @@ "should": "latest",

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