New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@root/greenlock

Package Overview
Dependencies
Maintainers
3
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@root/greenlock - npm Package Compare versions

Comparing version 3.0.1 to 3.0.2

2

bin/cli.js

@@ -181,3 +181,3 @@ 'use strict';

// trim leading and trailing '-'
bag = bag.replace(/^-+/g, '').replace(/-+$/g, '')
bag = bag.replace(/^-+/g, '').replace(/-+$/g, '');
return toCamel(bag) + 'Opts'; // '--bag-option-' => bagOptionOpts

@@ -184,0 +184,0 @@ }

@@ -7,4 +7,4 @@ #!/usr/bin/env node

if ('certonly' === args[0]) {
require('./certonly.js');
return;
require('./certonly.js');
return;
}

@@ -59,52 +59,39 @@ 'use strict';

return C._check(gnlck, mconf, db, args).then(function(pems) {
// No pems? get some!
if (!pems) {
return C._rawOrder(
gnlck,
mconf,
db,
acme,
chs,
acc,
email,
args
).then(function(newPems) {
// do not wait on notify
gnlck._notify('cert_issue', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: acc,
email: email,
pems: newPems
});
return newPems;
});
}
// Nice and fresh? We're done!
if (!C._isStale(gnlck, mconf, args, pems)) {
// return existing unexpired (although potentially stale) certificates when available
// there will be an additional .renewing property if the certs are being asynchronously renewed
//pems._type = 'current';
return pems;
if (pems) {
if (!C._isStale(gnlck, mconf, args, pems)) {
// return existing unexpired (although potentially stale) certificates when available
// there will be an additional .renewing property if the certs are being asynchronously renewed
//pems._type = 'current';
return pems;
}
}
// Getting stale? Let's renew to freshen up!
var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args).then(
function(renewedPems) {
// do not wait on notify
gnlck._notify('cert_renewal', {
options: args,
subject: args.subject,
altnames: args.altnames,
account: acc,
email: email,
pems: renewedPems
});
return renewedPems;
// We're either starting fresh or freshening up...
var p = C._rawOrder(gnlck, mconf, db, acme, chs, acc, email, args);
var evname = pems ? 'cert_renewal' : 'cert_issue';
p.then(function(newPems) {
// notify in the background
var renewAt = C._renewableAt(gnlck, mconf, args, newPems);
gnlck._notify(evname, {
renewAt: renewAt,
subject: args.subject,
altnames: args.altnames
});
}).catch(function(err) {
if (!err.context) {
err.context = evname;
}
);
err.subject = args.subject;
err.altnames = args.altnames;
gnlck._notify('error', err);
});
// TODO what should this be?
// No choice but to hang tight and wait for it
if (!pems) {
return p;
}
// Wait it out
// TODO should we call this waitForRenewal?
if (args.waitForRenewal) {

@@ -114,2 +101,3 @@ return p;

// Let the certs renew in the background
return pems;

@@ -182,2 +170,11 @@ });

.then(function(pems) {
var renewAt = C._renewableAt(gnlck, mconf, args, pems);
gnlck._notify('_cert_issue', {
renewAt: renewAt,
subject: args.subject,
altnames: args.altnames,
pems: pems
});
if (kresult.exists) {

@@ -184,0 +181,0 @@ return pems;

@@ -68,2 +68,3 @@ 'use strict';

greenlock.manager = Manager.create(defaults);
//console.log('debug greenlock.manager', Object.keys(greenlock.manager));
greenlock._init = function() {

@@ -74,3 +75,3 @@ var p;

};
p = greenlock.manager.config().then(function(conf) {
p = greenlock.manager.defaults().then(function(conf) {
var changed = false;

@@ -86,3 +87,3 @@ if (!conf.challenges) {

if (changed) {
return greenlock.manager.config(conf);
return greenlock.manager.defaults(conf);
}

@@ -160,2 +161,22 @@ });

var mng = greenlock.manager;
if ('_' === String(ev)[0]) {
if ('_cert_issue' === ev) {
try {
mng.update({
subject: params.subject,
renewAt: params.renewAt
}).catch(function(e) {
e.context = '_cert_issue';
greenlock._notify('error', e);
});
} catch (e) {
e.context = '_cert_issue';
greenlock._notify('error', e);
}
}
// trap internal events internally
return;
}
if (mng.notify || greenlock._defaults.notify) {

@@ -200,2 +221,3 @@ try {

UserEvents.notify({
/*
// maintainer should be only on pre-publish, or maybe install, I think

@@ -205,3 +227,3 @@ maintainerEmail: greenlock._defaults._maintainerEmail,

version: greenlock._defaults._maintainerPackageVersion,
action: params.pems._type,
//action: params.pems._type,
domains: params.altnames,

@@ -212,2 +234,3 @@ subscriberEmail: greenlock._defaults._subscriberEmail,

telemetry: greenlock._defaults.telemetry
*/
});

@@ -217,5 +240,89 @@ }

greenlock._single = function(args) {
if (!args.servername) {
return Promise.reject(new Error('no servername given'));
}
if (
args.servernames ||
args.subject ||
args.renewBefore ||
args.issueBefore ||
args.expiresBefore
) {
return Promise.reject(
new Error(
'bad arguments, did you mean to call greenlock.renew()?'
)
);
}
// duplicate, force, and others still allowed
return Promise.resolve(args);
};
greenlock.get = function(args) {
return greenlock
._single(args)
.then(function() {
args._includePems = true;
return greenlock.renew(args);
})
.then(function(results) {
if (!results || !results.length) {
return null;
}
// just get the first one
var result = results[0];
// (there should be only one, ideally)
if (results.length > 1) {
var err = new Error(
"a search for '" +
args.servername +
"' returned multiple certificates"
);
err.context = 'duplicate_certs';
err.servername = args.servername;
err.subjects = results.map(function(r) {
return (r.site || {}).subject || 'N/A';
});
greenlock._notify('warning', err);
}
if (result.error) {
return Promise.reject(result.error);
}
// site for plugin options, such as http-01 challenge
// pems for the obvious reasons
return result;
});
};
greenlock._config = function(args) {
return greenlock
._single(args)
.then(function() {
return greenlock.manager.find(args);
})
.then(function(sites) {
if (!sites || !sites.length) {
return null;
}
var site = sites[0];
site = JSON.parse(JSON.stringify(site));
if (!site.store) {
site.store = greenlock._defaults.store;
}
if (!site.challenges) {
site.challenges = greenlock._defaults.challenges;
}
return site;
});
};
// needs to get info about the renewal, such as which store and challenge(s) to use
greenlock.renew = function(args) {
return greenlock.manager.config().then(function(mconf) {
return greenlock.manager.defaults().then(function(mconf) {
return greenlock._renew(mconf, args);

@@ -237,11 +344,12 @@ });

if (args.domain) {
if (args.servername) {
// this doesn't have to be the subject, it can be anything
// however, not sure how useful this really is...
args.domain = args.toLowerCase();
args.servername = args.servername.toLowerCase();
}
args.defaults = greenlock.defaults;
//console.log('greenlock._renew find', args);
return greenlock.manager.find(args).then(function(sites) {
// Note: the manager must guaranteed that these are mutable copies
//console.log('greenlock._renew found', sites);

@@ -256,5 +364,3 @@ var renewedOrFailed = [];

var order = {
site: site
};
var order = { site: site };
renewedOrFailed.push(order);

@@ -265,7 +371,12 @@ // TODO merge args + result?

.then(function(pems) {
order.pems = pems;
if (args._includePems) {
order.pems = pems;
}
})
.catch(function(err) {
order.error = err;
// For greenlock express serialization
err.toJSON = errorToJSON;
err.context = err.context || 'cert_order';
err.subject = site.subject;

@@ -276,3 +387,3 @@ if (args.servername) {

// for debugging, but not to be relied on
err._order = order;
err._site = site;
// TODO err.context = err.context || 'renew_certificate'

@@ -327,3 +438,3 @@ greenlock._notify('error', err);

return greenlock._init().then(function() {
return greenlock.manager.config().then(function(mconf) {
return greenlock.manager.defaults().then(function(mconf) {
return greenlock._order(mconf, args);

@@ -334,10 +445,6 @@ });

greenlock._order = function(mconf, args) {
// packageAgent, maintainerEmail
return greenlock._acme(args).then(function(acme) {
var storeConf = args.store || greenlock._defaults.store;
return P._load(storeConf.module).then(function(plugin) {
var store = Greenlock._normalizeStore(
storeConf.module,
plugin.create(storeConf)
);
return P._loadStore(storeConf).then(function(store) {
return A._getOrCreate(

@@ -356,13 +463,3 @@ greenlock,

Object.keys(challengeConfs).map(function(typ01) {
var chConf = challengeConfs[typ01];
return P._load(chConf.module).then(function(
plugin
) {
var ch = Greenlock._normalizeChallenge(
chConf.module,
plugin.create(chConf)
);
ch._type = typ01;
return ch;
});
return P._loadChallenge(challengeConfs, typ01);
})

@@ -408,2 +505,4 @@ ).then(function(arr) {

G._loadChallenge = P._loadChallenge;
G._defaults = function(opts) {

@@ -522,110 +621,2 @@ var defaults = {};

Greenlock._normalizeStore = function(name, store) {
var acc = store.accounts;
var crt = store.certificates;
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// accs
if (acc.check && 2 === acc.check.length) {
warn();
acc._thunk_check = acc.check;
acc.check = promisify(acc._thunk_check);
}
if (acc.set && 3 === acc.set.length) {
warn();
acc._thunk_set = acc.set;
acc.set = promisify(acc._thunk_set);
}
if (2 === acc.checkKeypair.length) {
warn();
acc._thunk_checkKeypair = acc.checkKeypair;
acc.checkKeypair = promisify(acc._thunk_checkKeypair);
}
if (3 === acc.setKeypair.length) {
warn();
acc._thunk_setKeypair = acc.setKeypair;
acc.setKeypair = promisify(acc._thunk_setKeypair);
}
// certs
if (2 === crt.check.length) {
warn();
crt._thunk_check = crt.check;
crt.check = promisify(crt._thunk_check);
}
if (3 === crt.set.length) {
warn();
crt._thunk_set = crt.set;
crt.set = promisify(crt._thunk_set);
}
if (2 === crt.checkKeypair.length) {
warn();
crt._thunk_checkKeypair = crt.checkKeypair;
crt.checkKeypair = promisify(crt._thunk_checkKeypair);
}
if (2 === crt.setKeypair.length) {
warn();
crt._thunk_setKeypair = crt.setKeypair;
crt.setKeypair = promisify(crt._thunk_setKeypair);
}
return store;
};
Greenlock._normalizeChallenge = function(name, ch) {
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// init, zones, set, get, remove
if (ch.init && 2 === ch.init.length) {
warn();
ch._thunk_init = ch.init;
ch.init = promisify(ch._thunk_init);
}
if (ch.zones && 2 === ch.zones.length) {
warn();
ch._thunk_zones = ch.zones;
ch.zones = promisify(ch._thunk_zones);
}
if (2 === ch.set.length) {
warn();
ch._thunk_set = ch.set;
ch.set = promisify(ch._thunk_set);
}
if (2 === ch.remove.length) {
warn();
ch._thunk_remove = ch.remove;
ch.remove = promisify(ch._thunk_remove);
}
if (ch.get && 2 === ch.get.length) {
warn();
ch._thunk_get = ch.get;
ch.get = promisify(ch._thunk_get);
}
return ch;
};
function errorToJSON(e) {

@@ -632,0 +623,0 @@ var error = {};

@@ -1,97 +0,95 @@

var accountKeypair = await Keypairs.generate({ kty: accKty });
if (config.debug) {
console.info('Account Key Created');
console.info(JSON.stringify(accountKeypair, null, 2));
console.info();
console.info();
}
var accountKeypair = await Keypairs.generate({ kty: accKty });
if (config.debug) {
console.info('Account Key Created');
console.info(JSON.stringify(accountKeypair, null, 2));
console.info();
console.info();
}
var account = await acme.accounts.create({
agreeToTerms: agree,
// TODO detect jwk/pem/der?
accountKeypair: { privateKeyJwk: accountKeypair.private },
subscriberEmail: config.email
});
var account = await acme.accounts.create({
agreeToTerms: agree,
// TODO detect jwk/pem/der?
accountKeypair: { privateKeyJwk: accountKeypair.private },
subscriberEmail: config.email
});
// TODO top-level agree
function agree(tos) {
if (config.debug) {
console.info('Agreeing to Terms of Service:');
console.info(tos);
console.info();
console.info();
}
agreed = true;
return Promise.resolve(tos);
}
// TODO top-level agree
function agree(tos) {
if (config.debug) {
console.info('New Subscriber Account');
console.info(JSON.stringify(account, null, 2));
console.info('Agreeing to Terms of Service:');
console.info(tos);
console.info();
console.info();
}
if (!agreed) {
throw new Error('Failed to ask the user to agree to terms');
}
agreed = true;
return Promise.resolve(tos);
}
if (config.debug) {
console.info('New Subscriber Account');
console.info(JSON.stringify(account, null, 2));
console.info();
console.info();
}
if (!agreed) {
throw new Error('Failed to ask the user to agree to terms');
}
var certKeypair = await Keypairs.generate({ kty: srvKty });
var pem = await Keypairs.export({
jwk: certKeypair.private,
encoding: 'pem'
});
if (config.debug) {
console.info('Server Key Created');
console.info('privkey.jwk.json');
console.info(JSON.stringify(certKeypair, null, 2));
// This should be saved as `privkey.pem`
console.info();
console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
console.info(pem);
console.info();
}
var certKeypair = await Keypairs.generate({ kty: srvKty });
var pem = await Keypairs.export({
jwk: certKeypair.private,
encoding: 'pem'
});
if (config.debug) {
console.info('Server Key Created');
console.info('privkey.jwk.json');
console.info(JSON.stringify(certKeypair, null, 2));
// This should be saved as `privkey.pem`
console.info();
console.info('privkey.' + srvKty.toLowerCase() + '.pem:');
console.info(pem);
console.info();
}
// 'subject' should be first in list
var domains = randomDomains(rnd);
if (config.debug) {
console.info('Get certificates for random domains:');
console.info(
domains
.map(function(puny) {
var uni = punycode.toUnicode(puny);
if (puny !== uni) {
return puny + ' (' + uni + ')';
}
return puny;
})
.join('\n')
);
console.info();
}
// 'subject' should be first in list
var domains = randomDomains(rnd);
if (config.debug) {
console.info('Get certificates for random domains:');
console.info(
domains
.map(function(puny) {
var uni = punycode.toUnicode(puny);
if (puny !== uni) {
return puny + ' (' + uni + ')';
}
return puny;
})
.join('\n')
);
console.info();
}
// Create CSR
var csrDer = await CSR.csr({
jwk: certKeypair.private,
domains: domains,
encoding: 'der'
});
var csr = Enc.bufToUrlBase64(csrDer);
var csrPem = PEM.packBlock({
type: 'CERTIFICATE REQUEST',
bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
});
if (config.debug) {
console.info('Certificate Signing Request');
console.info(csrPem);
console.info();
}
// Create CSR
var csrDer = await CSR.csr({
jwk: certKeypair.private,
domains: domains,
encoding: 'der'
});
var csr = Enc.bufToUrlBase64(csrDer);
var csrPem = PEM.packBlock({
type: 'CERTIFICATE REQUEST',
bytes: csrDer /* { jwk: jwk, domains: opts.domains } */
});
if (config.debug) {
console.info('Certificate Signing Request');
console.info(csrPem);
console.info();
}
var results = await acme.certificates.create({
account: account,
accountKeypair: { privateKeyJwk: accountKeypair.private },
csr: csr,
domains: domains,
challenges: challenges, // must be implemented
customerEmail: null
});
var results = await acme.certificates.create({
account: account,
accountKeypair: { privateKeyJwk: accountKeypair.private },
csr: csr,
domains: domains,
challenges: challenges, // must be implemented
customerEmail: null
});
{
"name": "@root/greenlock",
"version": "3.0.1",
"version": "3.0.2",
"description": "The easiest Let's Encrypt client for Node.js and Browsers",

@@ -43,3 +43,3 @@ "homepage": "https://rootprojects.org/greenlock/",

"@root/request": "^1.3.10",
"acme-http-01-standalone": "^3.0.0",
"acme-http-01-standalone": "^3.0.5",
"cert-info": "^1.5.1",

@@ -46,0 +46,0 @@ "greenlock-manager-fs": "^0.6.0",

@@ -11,3 +11,18 @@ 'use strict';

P._load = function(modname) {
P._loadStore = function(storeConf) {
return P._loadHelper(storeConf.module).then(function(plugin) {
return P._normalizeStore(storeConf.module, plugin.create(storeConf));
});
};
P._loadChallenge = function(chConfs, typ01) {
return P._loadHelper(chConfs[typ01].module).then(function(plugin) {
var ch = P._normalizeChallenge(
chConfs[typ01].module,
plugin.create(chConfs[typ01])
);
ch._type = typ01;
return ch;
});
};
P._loadHelper = function(modname) {
try {

@@ -22,2 +37,146 @@ return Promise.resolve(require(modname));

P._normalizeStore = function(name, store) {
var acc = store.accounts;
var crt = store.certificates;
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
// accs
if (acc.check && 2 === acc.check.length) {
warn();
acc._thunk_check = acc.check;
acc.check = promisify(acc._thunk_check);
}
if (acc.set && 3 === acc.set.length) {
warn();
acc._thunk_set = acc.set;
acc.set = promisify(acc._thunk_set);
}
if (2 === acc.checkKeypair.length) {
warn();
acc._thunk_checkKeypair = acc.checkKeypair;
acc.checkKeypair = promisify(acc._thunk_checkKeypair);
}
if (3 === acc.setKeypair.length) {
warn();
acc._thunk_setKeypair = acc.setKeypair;
acc.setKeypair = promisify(acc._thunk_setKeypair);
}
// certs
if (2 === crt.check.length) {
warn();
crt._thunk_check = crt.check;
crt.check = promisify(crt._thunk_check);
}
if (3 === crt.set.length) {
warn();
crt._thunk_set = crt.set;
crt.set = promisify(crt._thunk_set);
}
if (2 === crt.checkKeypair.length) {
warn();
crt._thunk_checkKeypair = crt.checkKeypair;
crt.checkKeypair = promisify(crt._thunk_checkKeypair);
}
if (2 === crt.setKeypair.length) {
warn();
crt._thunk_setKeypair = crt.setKeypair;
crt.setKeypair = promisify(crt._thunk_setKeypair);
}
return store;
};
P._normalizeChallenge = function(name, ch) {
var gch = {};
var warned = false;
function warn() {
if (warned) {
return;
}
warned = true;
console.warn(
"'" +
name +
"' may have incorrect function signatures, or contains deprecated use of callbacks"
);
}
var warned2 = false;
function warn2() {
if (warned2) {
return;
}
warned2 = true;
console.warn(
"'" +
name +
"' did not return a Promise when called. This should be fixed by the maintainer."
);
}
function wrappy(fn) {
return function(_params) {
return Promise.resolve().then(function() {
var result = fn.call(ch, _params);
if (!result || !result.then) {
warn2();
}
return result;
});
};
}
// init, zones, set, get, remove
if (ch.init) {
if (2 === ch.init.length) {
warn();
ch._thunk_init = ch.init;
ch.init = promisify(ch._thunk_init);
}
gch.init = wrappy(ch.init);
}
if (ch.zones) {
if (2 === ch.zones.length) {
warn();
ch._thunk_zones = ch.zones;
ch.zones = promisify(ch._thunk_zones);
}
gch.zones = wrappy(ch.zones);
}
if (2 === ch.set.length) {
warn();
ch._thunk_set = ch.set;
ch.set = promisify(ch._thunk_set);
}
gch.set = wrappy(ch.set);
if (2 === ch.remove.length) {
warn();
ch._thunk_remove = ch.remove;
ch.remove = promisify(ch._thunk_remove);
}
gch.remove = wrappy(ch.remove);
if (ch.get) {
if (2 === ch.get.length) {
warn();
ch._thunk_get = ch.get;
ch.get = promisify(ch._thunk_get);
}
gch.get = wrappy(ch.get);
}
return gch;
};
P._loadSync = function(modname) {

@@ -24,0 +183,0 @@ var mod;

@@ -58,9 +58,24 @@ # @root/greenlock

// Optionally, you may receive other (very few) updates, such as important new features
maintainerEmail: 'jon@example.com',
maintainerUpdates: true, // default: false
maintainerEmail: 'jon@example.com'
});
```
| Parameter | Description |
| --------------- | ------------------------------------------------------------------------------------ |
| maintainerEmail | the developer contact for critical bug and security notifications |
| packageAgent | if you publish your package for others to use, `require('./package.json').name` here |
<!--
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
maintainerUpdates: true // default: false
-->
### Add Approved Domains
```js
greenlock.manager.defaults({
// The "Let's Encrypt Subscriber" (often the same as the maintainer)
// NOT the end customer (except where that is also the maintainer)
subscriberEmail: 'jon@example.com',
agreeToTerms: true // default: false
agreeToTerms: true
});

@@ -71,12 +86,3 @@ ```

| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| servername | the default servername to use for non-sni requests (many IoT clients) |
| maintainerEmail | the developer contact for critical bug and security notifications |
| maintainerUpdates | (default: false) receive occasional non-critical notifications |
| maintainerPackage | if you publish your package for others to use, `require('./package.json').name` here |
| maintainerPackageVersion | if you publish your package for others to use, `require('./package.json').version` here |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| agreeToTerms | (default: false) either 'true' or a function that presents the Terms of Service and returns it once accepted |
| store | override the default storage module |
| store.module | the name of your storage module |
| store.xxxx | options specific to your storage module |
| challenges['http-01'] | provide an http-01 challenge module |

@@ -87,5 +93,14 @@ | challenges['dns-01'] | provide a dns-01 challenge module |

| challenges[type].xxxx | module-specific options |
| servername | the default servername to use for non-sni requests (many IoT clients) |
| subscriberEmail | the contact who agrees to the Let's Encrypt Subscriber Agreement and the Greenlock Terms of Service<br>this contact receives renewal failure notifications |
| store | override the default storage module |
| store.module | the name of your storage module |
| store.xxxx | options specific to your storage module |
### Add Approved Domains
<!--
| serverId | an arbitrary name to distinguish this server within a cluster of servers |
-->
```js

@@ -110,22 +125,20 @@ gl.add({

```js
return greenlock
.renew()
.then(function(pems) {
console.info(pems);
})
.then(function(results) {
results.forEach(function(site) {
if (site.error) {
console.error(site.subject, site.error);
return;
}
});
return greenlock.renew().then(function(results) {
results.forEach(function(site) {
if (site.error) {
console.error(site.subject, site.error);
return;
}
});
});
```
| Parameter | Type | Description |
| ---------- | ---- | ---------------------------------------------------------- |
| (optional) | - | ALL parameters are optional, but some should be paired |
| force | bool | force silly options, such as tiny durations |
| duplicate | bool | force the domain to renew, regardless of age or expiration |
| Parameter | Type | Description |
| ------------- | ---- | ------------------------------------------------------------------------------- |
| (optional) | | ALL parameters are optional, but some should be paired |
| force | bool | force silly options, such as tiny durations |
| duplicate | bool | force the domain to renew, regardless of age or expiration |
| issuedBefore | ms | Check domains issued before the given date in milliseconds |
| expiresBefore | ms | Check domains that expire before the given date in milliseconds |
| renewBefore | ms | Check domains that are scheduled to renew before the given date in milliseconds |

@@ -132,0 +145,0 @@ <!--

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