Socket
Socket
Sign inDemoInstall

dom-delegate

Package Overview
Dependencies
Maintainers
2
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

dom-delegate - npm Package Compare versions

Comparing version 0.2.1 to 0.3.0

build/dom-delegate.js

2

component.json
{
"name": "dom-delegate",
"description": "Create and manage a DOM event delegator.",
"version": "0.2.1",
"version": "0.3.0",
"main": "lib/delegate.js",

@@ -6,0 +6,0 @@ "scripts": [

@@ -1,433 +0,424 @@

/*jslint browser:true, node:true*/
/*global define, Node*/
/*jshint browser:true, node:true*/
'use strict';
module.exports = Delegate;
/**
* @preserve Create and manage a DOM event delegator.
* DOM event delegator
*
* @version 0.1.5
* @codingstandard ftlabs-jsv2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
* The delegator will listen
* for events that bubble up
* to the root node.
*
* @constructor
* @param {Node|string} [root] The root node or a selector string matching the root node
*/
function Delegate(root) {
var self = this;
;(function(){
if (root) {
this.root(root);
}
'use strict';
/**
* Maintain a map of listener
* lists, keyed by event name.
*
* @type Object
*/
this.listenerMap = {};
/**
* DOM event delegator
*
* The delegator will listen
* for events that bubble up
* to the root node.
*
* @constructor
* @param {Node|string} [root] The root node or a selector string matching the root node
*/
function Delegate(root) {
var self = this;
/** @type function() */
this.handle = function(event) { Delegate.prototype.handle.call(self, event); };
}
if (root) {
this.root(root);
}
/**
* @protected
* @type ?boolean
*/
Delegate.tagsCaseSensitive = null;
/**
* Maintain a map of listener
* lists, keyed by event name.
*
* @type Object
*/
this.listenerMap = {};
/**
* Start listening for events
* on the provided DOM element
*
* @param {Node|string} [root] The root node or a selector string matching the root node
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.root = function(root) {
var listenerMap = this.listenerMap;
var eventType;
/** @type function() */
this.handle = function(event) { Delegate.prototype.handle.call(self, event); };
}
if (typeof root === 'string') {
root = document.querySelector(root);
}
/**
* @protected
* @type ?boolean
*/
Delegate.tagsCaseSensitive = null;
// Remove master event listeners
if (this.rootElement) {
for (eventType in listenerMap) {
if (listenerMap.hasOwnProperty(eventType)) {
this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
}
/**
* Start listening for events
* on the provided DOM element
*
* @param {Node|string} [root] The root node or a selector string matching the root node
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.root = function(root) {
var listenerMap = this.listenerMap;
var eventType;
// If no root or root is not
// a dom node, then remove internal
// root reference and exit here
if (!root || !root.addEventListener) {
if (this.rootElement) {
delete this.rootElement;
}
return this;
}
if (typeof root === 'string') {
root = document.querySelector(root);
}
/**
* The root node at which
* listeners are attached.
*
* @type Node
*/
this.rootElement = root;
// Remove master event listeners
if (this.rootElement) {
for (eventType in listenerMap) {
if (listenerMap.hasOwnProperty(eventType)) {
this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
}
// Set up master event listeners
for (eventType in listenerMap) {
if (listenerMap.hasOwnProperty(eventType)) {
this.rootElement.addEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
// If no root or root is not
// a dom node, then remove internal
// root reference and exit here
if (!root || !root.addEventListener) {
if (this.rootElement) {
delete this.rootElement;
}
return this;
}
return this;
};
/**
* The root node at which
* listeners are attached.
*
* @type Node
*/
this.rootElement = root;
/**
* @param {string} eventType
* @returns boolean
*/
Delegate.prototype.captureForType = function(eventType) {
return eventType === 'error';
};
// Set up master event listeners
for (eventType in listenerMap) {
if (listenerMap.hasOwnProperty(eventType)) {
this.rootElement.addEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
/**
* Attach a handler to one
* event for all elements
* that match the selector,
* now or in the future
*
* The handler function receives
* three arguments: the DOM event
* object, the node that matched
* the selector while the event
* was bubbling and a reference
* to itself. Within the handler,
* 'this' is equal to the second
* argument.
*
* The node that actually received
* the event can be accessed via
* 'event.target'.
*
* @param {string} eventType Listen for these events (in a space-separated list)
* @param {string|undefined} selector Only handle events on elements matching this selector, if undefined match root element
* @param {function()} handler Handler function - event data passed here will be in event.data
* @param {Object} [eventData] Data to pass in event.data
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.on = function(eventType, selector, handler, eventData) {
var root, listenerMap, matcher, matcherParam, self = this;
return this;
};
if (!eventType) {
throw new TypeError('Invalid event type: ' + eventType);
}
/**
* @param {string} eventType
* @returns boolean
*/
Delegate.prototype.captureForType = function(eventType) {
return eventType === 'error';
};
// handler can be passed as
// the second or third argument
if (typeof selector === 'function') {
handler = selector;
selector = null;
eventData = handler;
}
/**
* Attach a handler to one
* event for all elements
* that match the selector,
* now or in the future
*
* The handler function receives
* three arguments: the DOM event
* object, the node that matched
* the selector while the event
* was bubbling and a reference
* to itself. Within the handler,
* 'this' is equal to the second
* argument.
*
* The node that actually received
* the event can be accessed via
* 'event.target'.
*
* @param {string} eventType Listen for these events (in a space-separated list)
* @param {string|undefined} selector Only handle events on elements matching this selector, if undefined match root element
* @param {function()} handler Handler function - event data passed here will be in event.data
* @param {Object} [eventData] Data to pass in event.data
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.on = function(eventType, selector, handler, eventData) {
var root, listenerMap, matcher, matcherParam, self = this;
// Normalise undefined eventData to null
if (eventData === undefined) {
eventData = null;
}
if (!eventType) {
throw new TypeError('Invalid event type: ' + eventType);
}
if (typeof handler !== 'function') {
throw new TypeError('Handler must be a type of Function');
}
// Normalise undefined eventData to null
if (eventData === undefined) {
eventData = null;
}
root = this.rootElement;
listenerMap = this.listenerMap;
if (typeof handler !== 'function') {
throw new TypeError('Handler must be a type of Function');
}
// Add master handler for type if not created yet
if (!listenerMap[eventType]) {
if (root) {
root.addEventListener(eventType, this.handle, this.captureForType(eventType));
}
listenerMap[eventType] = [];
}
root = this.rootElement;
listenerMap = this.listenerMap;
if (!selector) {
matcherParam = null;
// Add master handler for type if not created yet
if (!listenerMap[eventType]) {
if (root) {
root.addEventListener(eventType, this.handle, this.captureForType(eventType));
}
listenerMap[eventType] = [];
}
// COMPLEX - matchesRoot needs to have access to
// this.rootElement, so bind the function to this.
matcher = this.matchesRoot.bind(this);
if (!selector) {
matcherParam = null;
// Compile a matcher for the given selector
} else if (/^[a-z]+$/i.test(selector)) {
// COMPLEX - matchesRoot needs to have access to
// this.rootElement, so bind the function to this.
matcher = this.matchesRoot.bind(this);
// Lazily check whether tag names are case sensitive (as in XML or XHTML documents).
if (Delegate.tagsCaseSensitive === null) {
Delegate.tagsCaseSensitive = document.createElement('i').tagName === 'i';
}
// Compile a matcher for the given selector
} else if (/^[a-z]+$/i.test(selector)) {
if (!Delegate.tagsCaseSensitive) {
matcherParam = selector.toUpperCase();
} else {
matcherParam = selector;
}
// Lazily check whether tag names are case sensitive (as in XML or XHTML documents).
if (Delegate.tagsCaseSensitive === null) {
Delegate.tagsCaseSensitive = document.createElement('i').tagName === 'i';
}
matcher = this.matchesTag;
} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
matcherParam = selector.slice(1);
matcher = this.matchesId;
} else {
matcherParam = selector;
matcher = this.matches;
}
if (!Delegate.tagsCaseSensitive) {
matcherParam = selector.toUpperCase();
} else {
matcherParam = selector;
}
// Add to the list of listeners
listenerMap[eventType].push({
selector: selector,
eventData: eventData,
handler: handler,
matcher: matcher,
matcherParam: matcherParam
});
matcher = this.matchesTag;
} else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
matcherParam = selector.slice(1);
matcher = this.matchesId;
} else {
matcherParam = selector;
matcher = this.matches;
}
return this;
};
// Add to the list of listeners
listenerMap[eventType].push({
selector: selector,
eventData: eventData,
handler: handler,
matcher: matcher,
matcherParam: matcherParam
});
/**
* Remove an event handler
* for elements that match
* the selector, forever
*
* @param {string} [eventType] Remove handlers for events matching this type, considering the other parameters
* @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
* @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.off = function(eventType, selector, handler) {
var i, listener, listenerMap, listenerList, singleEventType, self = this;
return this;
};
// Handler can be passed as
// the second or third argument
if (typeof selector === 'function') {
handler = selector;
selector = null;
}
/**
* Remove an event handler
* for elements that match
* the selector, forever
*
* @param {string} [eventType] Remove handlers for events matching this type, considering the other parameters
* @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
* @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
* @returns {Delegate} This method is chainable
*/
Delegate.prototype.off = function(eventType, selector, handler) {
var i, listener, listenerMap, listenerList, singleEventType, self = this;
listenerMap = this.listenerMap;
if (!eventType) {
for (singleEventType in listenerMap) {
if (listenerMap.hasOwnProperty(singleEventType)) {
this.off(singleEventType, selector, handler);
}
}
listenerMap = this.listenerMap;
if (!eventType) {
for (singleEventType in listenerMap) {
if (listenerMap.hasOwnProperty(singleEventType)) {
this.off(singleEventType, selector, handler);
}
}
return this;
}
return this;
}
listenerList = listenerMap[eventType];
if (!listenerList || !listenerList.length) {
return this;
}
listenerList = listenerMap[eventType];
if (!listenerList || !listenerList.length) {
return this;
}
// Remove only parameter matches
// if specified
for (i = listenerList.length - 1; i >= 0; i--) {
listener = listenerList[i];
// Remove only parameter matches if specified
for (i = listenerList.length - 1; i >= 0; i--) {
listener = listenerList[i];
if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
listenerList.splice(i, 1);
}
}
if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
listenerList.splice(i, 1);
}
}
// All listeners removed
if (!listenerList.length) {
delete listenerMap[eventType];
// All listeners removed
if (!listenerList.length) {
delete listenerMap[eventType];
// Remove the main handler
if (this.rootElement) {
this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
// Remove the main handler
if (this.rootElement) {
this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
return this;
};
return this;
};
/**
* Handle an arbitrary event.
*
* @param {Event} event
*/
Delegate.prototype.handle = function(event) {
var i, l, root, listener, returned, listenerList, target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore';
/**
* Handle an arbitrary event.
*
* @param {Event} event
*/
Delegate.prototype.handle = function(event) {
var i, l, root, listener, returned, listenerList, target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore';
if (event[EVENTIGNORE] === true) {
return;
}
if (event[EVENTIGNORE] === true) {
return;
}
target = event.target;
if (target.nodeType === Node.TEXT_NODE) {
target = target.parentNode;
}
target = event.target;
if (target.nodeType === Node.TEXT_NODE) {
target = target.parentNode;
}
root = this.rootElement;
listenerList = this.listenerMap[event.type];
root = this.rootElement;
listenerList = this.listenerMap[event.type];
// Need to continuously check
// that the specific list is
// still populated in case one
// of the callbacks actually
// causes the list to be destroyed.
l = listenerList.length;
while (target && l) {
for (i = 0; i < l; i++) {
listener = listenerList[i];
// Need to continuously check
// that the specific list is
// still populated in case one
// of the callbacks actually
// causes the list to be destroyed.
l = listenerList.length;
while (target && l) {
for (i = 0; i < l; i++) {
listener = listenerList[i];
// Bail from this loop if
// the length changed and
// no more listeners are
// defined between i and l.
if (!listener) {
break;
}
// Bail from this loop if
// the length changed and
// no more listeners are
// defined between i and l.
if (!listener) {
break;
}
// Check for match and fire
// the event if there's one
//
// TODO:MCG:20120117: Need a way
// to check if event#stopImmediateProgagation
// was called. If so, break both loops.
if (listener.matcher.call(target, listener.matcherParam, target)) {
returned = this.fire(event, target, listener);
}
// Check for match and fire
// the event if there's one
//
// TODO:MCG:20120117: Need a way
// to check if event#stopImmediateProgagation
// was called. If so, break both loops.
if (listener.matcher.call(target, listener.matcherParam, target)) {
returned = this.fire(event, target, listener);
}
// Stop propagation to subsequent
// callbacks if the callback returned
// false
if (returned === false) {
event[EVENTIGNORE] = true;
return;
}
}
// Stop propagation to subsequent
// callbacks if the callback returned
// false
if (returned === false) {
event[EVENTIGNORE] = true;
return;
}
}
// TODO:MCG:20120117: Need a way to
// check if event#stopProgagation
// was called. If so, break looping
// through the DOM. Stop if the
// delegation root has been reached
if (target === root) {
break;
}
// TODO:MCG:20120117: Need a way to
// check if event#stopProgagation
// was called. If so, break looping
// through the DOM. Stop if the
// delegation root has been reached
if (target === root) {
break;
}
l = listenerList.length;
target = target.parentElement;
}
};
l = listenerList.length;
target = target.parentElement;
}
};
/**
* Fire a listener on a target.
*
* @param {Event} event
* @param {Node} target
* @param {Object} listener
* @returns {boolean}
*/
Delegate.prototype.fire = function(event, target, listener) {
var returned, oldData;
/**
* Fire a listener on a target.
*
* @param {Event} event
* @param {Node} target
* @param {Object} listener
* @returns {boolean}
*/
Delegate.prototype.fire = function(event, target, listener) {
var returned, oldData;
if (listener.eventData !== null) {
oldData = event.data;
event.data = listener.eventData;
returned = listener.handler.call(target, event, target);
event.data = oldData;
} else {
returned = listener.handler.call(target, event, target);
}
if (listener.eventData !== null) {
oldData = event.data;
event.data = listener.eventData;
returned = listener.handler.call(target, event, target);
event.data = oldData;
} else {
returned = listener.handler.call(target, event, target);
}
return returned;
};
return returned;
};
/**
* Check whether an element
* matches a generic selector.
*
* @type function()
* @param {string} selector A CSS selector
*/
Delegate.prototype.matches = (function(el) {
if (!el) return;
var p = el.prototype;
return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
}(HTMLElement));
/**
* Check whether an element matches a generic selector.
*
* @type function()
* @param {string} selector A CSS selector
*/
Delegate.prototype.matches = (function(p) {
return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
}(HTMLElement.prototype));
/**
* Check whether an element
* matches a tag selector.
*
* Tags are NOT case-sensitive,
* except in XML (and XML-based
* languages such as XHTML).
*
* @param {string} tagName The tag name to test against
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesTag = function(tagName, element) {
return tagName === element.tagName;
};
/**
* Check whether an element
* matches a tag selector.
*
* Tags are NOT case-sensitive,
* except in XML (and XML-based
* languages such as XHTML).
*
* @param {string} tagName The tag name to test against
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesTag = function(tagName, element) {
return tagName === element.tagName;
};
/**
* Check whether an element
* matches the root.
*
* @param {?String} selector In this case this is always passed through as null and not used
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesRoot = function(selector, element) {
return this.rootElement === element;
};
/**
* Check whether an element matches the root.
*
* @param {?String} selector In this case this is always passed through as null and not used
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesRoot = function(selector, element) {
return this.rootElement === element;
};
/**
* Check whether the ID of
* the element in 'this'
* matches the given ID.
*
* IDs are case-sensitive.
*
* @param {string} id The ID to test against
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesId = function(id, element) {
return id === element.id;
};
/**
* Check whether the ID of
* the element in 'this'
* matches the given ID.
*
* IDs are case-sensitive.
*
* @param {string} id The ID to test against
* @param {Element} element The element to test with
* @returns boolean
*/
Delegate.prototype.matchesId = function(id, element) {
return id === element.id;
};
/**
* Short hand for off()
* and root(), ie both
* with no parameters
*
* @return void
*/
Delegate.prototype.destroy = function() {
this.off();
this.root();
};
/**
* Expose `Delegate`
*/
if (typeof module === "object") {
module.exports = function(root) {
return new Delegate(root);
};
module.exports.Delegate = Delegate;
} else if (typeof define === "function" && define.amd) {
define(function() {
return Delegate;
});
} else {
window.Delegate = Delegate;
}
})();
/**
* Short hand for off()
* and root(), ie both
* with no parameters
*
* @return void
*/
Delegate.prototype.destroy = function() {
this.off();
this.root();
};
{
"name": "dom-delegate",
"version": "0.2.1",
"version": "0.3.0",
"author": "FT Labs <enquiries@labs.ft.com> (http://labs.ft.com/)",

@@ -11,3 +11,3 @@ "description": "Create and manage a DOM event delegator.",

],
"main": "lib/delegate.js",
"main": "lib/index.js",
"repository": {

@@ -17,4 +17,7 @@ "type": "git",

},
"engines": {
"node": "*"
},
"scripts": {
"test": "node_modules/.bin/buster-test -c _tests/buster.js"
"test": "./node_modules/.bin/buster test"
},

@@ -28,3 +31,7 @@ "keywords": [

"buster": "~0.6.2",
"buster-coverage": "*"
"grunt": "~0.4.1",
"grunt-cli": "~0.1.8",
"grunt-buster": "~0.1.2",
"grunt-browserify": "1.0.2",
"grunt-contrib-uglify": "~0.1.2"
},

@@ -31,0 +38,0 @@ "license": "MIT",

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

# Delegate #
# dom-delegate [![Build Status](https://travis-ci.org/ftlabs/dom-delegate.png?branch=master)](https://travis-ci.org/ftlabs/dom-delegate)
[![Build Status](https://travis-ci.org/ftlabs/dom-delegate.png?branch=master)](https://travis-ci.org/ftlabs/dom-delegate)
FT's dom-delegate is a simple, easy-to-use component for binding to events on all target elements matching the given selector, irrespective of whether anything exists in the DOM at registration time or not. This allows developers to implement the [event delegation pattern](http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/).
Delegate is a simple, easy-to-use component for binding to events on all target elements matching the given selector, irrespective of whether they exist at registration time or not. This allows developers to implement the [event delegation pattern](http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/).
Delegate is developed by [FT Labs](http://labs.ft.com/), part of the Financial Times.

@@ -25,20 +23,57 @@

## Usage ##
## Installation ##
Include delegate.js in your JavaScript bundle or add it to your HTML page like this:
```
npm install dom-delegate
```
```html
<script type='application/javascript' src='/path/to/delegate.js'></script>
or
```
bower install dom-delegate
```
or
Download the [production version](http://github.com/ftlabs/dom-delegate/raw/master/build/dom-delegate.min.js) (<1k gzipped) or the [development version](http://github.com/ftlabs/dom-delegate/raw/master/build/dom-delegate.js).
## Usage ##
The script must be loaded prior to instantiating a Delegate object.
To instantiate Delegate on the `body`:
To instantiate Delegate on the `body` and listen to some events:
```js
function handleButtonClicks(event) {
// do some things
}
function handleTouchMove(event) {
// do some other things
}
window.addEventListener('load', function() {
new Delegate(document.body);
var delegate = new Delegate(document.body);
delegate.on('click', 'button', handleButtonClicks);
// Listen to all touch move
// events that reach the body
delegate.on('touchmove', handleTouchMove);
}, false);
```
A cool trick to handle images that fail to load:
```js
function handleImageFail() {
this.style.display = 'none';
}
window.addEventListener('load', function() {
var delegate = new Delegate(document.body);
delegate.on('error', 'img', handleImageFail);
}, false);
```
Note: as of 0.1.2 you do not need to provide a DOM element at the point of instantiation, it can be set later via the `root` method.

@@ -52,21 +87,9 @@

### AMD ###
Delegate has AMD (Asynchronous Module Definition) support. This allows it to be lazy-loaded with an AMD loader, such as [RequireJS](http://requirejs.org/).
### Component ###
Delegate comes with support for installation via the [Component package manager](https://github.com/component/component).
### NPM ###
Installation via the [Node Package Manager](https://npmjs.org/package/dom-delegate) is supported, although Component is preferred as this is not strictly a Node package.
## Tests ##
Tests are run using [buster](http://docs.busterjs.org/en/latest/) and sit in `_tests/`. To run the tests statically:
Tests are run using [buster](http://docs.busterjs.org/en/latest/) and sit in `test/`. To run the tests statically:
```
$ cd dom-delegate/
$ buster static -c _tests/buster.js
$ buster static -c test/buster.js
Starting server on http://localhost:8282/

@@ -85,3 +108,3 @@ ```

```
$ buster test -c _tests/buster.js
$ buster test -c test/buster.js
```

@@ -99,9 +122,9 @@

#### `selector (string)` ####
#### `selector (string|function)` ####
Any kind of valid CSS selector supported by [`matchesSelector`](http://caniuse.com/matchesselector). Some selectors, like `#id` or `tag` will use optimized functions internally that check for straight matches between the ID or tag name of elements.
Null is also accepted and will match the root element set by `root()`.
`null` is also accepted and will match the root element set by `root()`. Passing a handler function into `.on`'s second argument (with `eventData` as an optional third parameter) is equivalent to `.on(eventType, null, handler[, eventData])`.
#### `handler (function)` ####
#### `handler (function|*)` ####

@@ -122,6 +145,8 @@ Function that will handle the specified event on elements matching the given selector. The function will receive two arguments: the native event object and the target element, in that order.

#### `selector (string)` ####
#### `selector (string|function)` ####
Only remove listeners registered with the given selector, among the other arguments.
If null passed listeners registered to the root element will be removed. Passing in a function into `off`'s second parameter is equivalent to `.off(eventType, null, handler)` (the third parameter will be ignored).
#### `handler (function)` ####

@@ -128,0 +153,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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