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

@lwc/ssr-compiler

Package Overview
Dependencies
Maintainers
15
Versions
55
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lwc/ssr-compiler - npm Package Compare versions

Comparing version 8.1.0 to 8.1.1

dist/compile-js/stylesheet-scope-token.d.ts

4

dist/compile-js/stylesheets.d.ts
import type { NodePath } from 'estree-toolkit';
import type { Program, ImportDeclaration } from 'estree';
import type { ImportDeclaration } from 'estree';
import type { ComponentMetaState } from './types';

@@ -8,3 +8,3 @@ export declare function catalogStyleImport(path: NodePath<ImportDeclaration>, state: ComponentMetaState): void;

*/
export declare function addStylesheetImports(ast: Program, state: ComponentMetaState, filepath: string): void;
export declare function getStylesheetImports(filepath: string): ImportDeclaration[];
export declare function catalogStaticStylesheets(ids: string[], state: ComponentMetaState): void;

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

export default function compileTemplate(src: string, _filename: string): {
export default function compileTemplate(src: string, filename: string): {
code: string;
};

@@ -15,2 +15,3 @@ /**

var templateCompiler = require('@lwc/template-compiler');
var builders = require('estree-toolkit/dist/builders');
var util = require('util');

@@ -187,2 +188,5 @@

`;
const bDefaultScopedStyleImport = esTemplate `
import defaultScopedStylesheets from '${estreeToolkit.is.literal}';
`;
function catalogStyleImport(path, state) {

@@ -199,15 +203,14 @@ const specifier = path.node.specifiers[0];

}
const componentNamePattern = /(?<componentName>[^/]+)\.[tj]s$/;
/**
* This adds implicit style imports to the compiled component artifact.
*/
function addStylesheetImports(ast, state, filepath) {
const componentName = componentNamePattern.exec(filepath)?.groups?.componentName;
if (!componentName) {
throw new Error(`Could not determine component name from file path: ${filepath}`);
function getStylesheetImports(filepath) {
const moduleName = /(?<moduleName>[^/]+)\.html$/.exec(filepath)?.groups?.moduleName;
if (!moduleName) {
throw new Error(`Could not determine module name from file path: ${filepath}`);
}
if (state.cssExplicitImports || state.staticStylesheetIds) {
throw new Error(`Unimplemented static stylesheets, but found:\n${[...state.cssExplicitImports].join(' \n')}`);
}
ast.body.unshift(bDefaultStyleImport(estreeToolkit.builders.literal(`./${componentName}.css`)));
return [
bDefaultStyleImport(estreeToolkit.builders.literal(`./${moduleName}.css`)),
bDefaultScopedStyleImport(estreeToolkit.builders.literal(`./${moduleName}.scoped.css?scoped=true`)),
];
}

@@ -263,7 +266,8 @@ function catalogStaticStylesheets(ids, state) {

instance.connectedCallback?.();
const tmplFn = ${isIdentOrRenderCall} ?? __fallbackTmpl;
yield \`<\${tagName}\`;
yield tmplFn.stylesheetScopeTokenHostClass;
yield *__renderAttrs(attrs)
yield '>';
const tmplFn = ${isIdentOrRenderCall} ?? __fallbackTmpl;
yield* tmplFn(props, attrs, slotted, ${estreeToolkit.is.identifier}, instance, defaultStylesheets);
yield* tmplFn(props, attrs, slotted, ${estreeToolkit.is.identifier}, instance);
yield \`</\${tagName}>\`;

@@ -452,4 +456,6 @@ }

}
if (state.cssExplicitImports || state.staticStylesheetIds) {
throw new Error(`Unimplemented static stylesheets, but found:\n${[...state.cssExplicitImports].join(' \n')}`);
}
addGenerateMarkupExport(ast, state, filename);
addStylesheetImports(ast, state, filename);
return {

@@ -460,16 +466,40 @@ code: astring.generate(ast, {}),

/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
const Comment = function Comment(node, cxt) {
if (cxt.templateOptions.preserveComments) {
return [estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(estreeToolkit.builders.literal(`<!--${node.value}-->`)))];
}
else {
return [];
}
};
function generateStylesheetScopeToken(filename) {
// FIXME: we should be getting the namespace/name from the config options,
// since these actually come from the component filename, not the template filename.
const split = filename.split('/');
const namespace = split.at(-3);
const baseName = split.at(-1);
const componentName = baseName.replace(/\.[^.]+$/, '');
const {
// FIXME: handle legacy scope token for older API versions
scopeToken, } = templateCompiler.generateScopeTokens(filename, namespace, componentName);
return scopeToken;
}
const bStylesheetTokenDeclaration = esTemplate `
const stylesheetScopeToken = '${estreeToolkit.is.literal}';
`;
const bAdditionalDeclarations = [
esTemplate `
const hasScopedStylesheets = defaultScopedStylesheets && defaultScopedStylesheets.length > 0;
`,
esTemplate `
const stylesheetScopeTokenClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}"\` : '';
`,
esTemplate `
const stylesheetScopeTokenHostClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}-host"\` : '';
`,
esTemplate `
const stylesheetScopeTokenClassPrefix = hasScopedStylesheets ? (stylesheetScopeToken + ' ') : '';
`,
];
// Scope tokens are associated with a given template. This is assigned here so that it can be used in `generateMarkup`.
const tmplAssignmentBlock = esTemplate `
${estreeToolkit.is.identifier}.stylesheetScopeTokenHostClass = stylesheetScopeTokenHostClass;
`;
function addScopeTokenDeclarations(program, filename) {
const scopeToken = generateStylesheetScopeToken(filename);
program.body.unshift(bStylesheetTokenDeclaration(builders.builders.literal(scopeToken)), ...bAdditionalDeclarations.map((declaration) => declaration()));
program.body.push(tmplAssignmentBlock(builders.builders.identifier('tmpl')));
}

@@ -534,2 +564,17 @@ /*

*/
const Comment = function Comment(node, cxt) {
if (cxt.templateOptions.preserveComments) {
return [estreeToolkit.builders.expressionStatement(estreeToolkit.builders.yieldExpression(estreeToolkit.builders.literal(`<!--${node.value}-->`)))];
}
else {
return [];
}
};
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
function getRootMemberExpression$1(node) {

@@ -625,2 +670,3 @@ return node.object.type === 'MemberExpression' ? getRootMemberExpression$1(node.object) : node;

{
const prefix = (${ /* isClass */estreeToolkit.is.literal} && stylesheetScopeTokenClassPrefix) || '';
const attrOrPropValue = ${estreeToolkit.is.expression};

@@ -631,3 +677,3 @@ const valueType = typeof attrOrPropValue;

if (valueType === 'string') {
yield '="' + htmlEscape(attrOrPropValue, true) + '"';
yield \`="\${prefix}\${htmlEscape(attrOrPropValue, true)}"\`;
}

@@ -637,7 +683,13 @@ }

`;
function yieldAttrOrPropLiteralValue(name, valueNode) {
const bStringLiteralYield = esTemplateWithYield `
{
const prefix = (${ /* isClass */estreeToolkit.is.literal} && stylesheetScopeTokenClassPrefix) || '';
yield ' ' + ${estreeToolkit.is.literal} + '="' + prefix + "${estreeToolkit.is.literal}" + '"'
}
`;
function yieldAttrOrPropLiteralValue(name, valueNode, isClass) {
const { value, type } = valueNode;
if (typeof value === 'string') {
const yieldedValue = name === 'style' ? cleanStyleAttrVal(value) : value;
return [bYield$1(estreeToolkit.builders.literal(` ${name}="${yieldedValue}"`))];
return [bStringLiteralYield(estreeToolkit.builders.literal(isClass), estreeToolkit.builders.literal(name), estreeToolkit.builders.literal(yieldedValue))];
}

@@ -649,5 +701,5 @@ else if (typeof value === 'boolean') {

}
function yieldAttrOrPropLiveValue(name, value) {
function yieldAttrOrPropLiveValue(name, value, isClass) {
const instanceMemberRef = estreeToolkit.builders.memberExpression(estreeToolkit.builders.identifier('instance'), value);
return [bConditionalLiveYield(instanceMemberRef, estreeToolkit.builders.literal(name))];
return [bConditionalLiveYield(estreeToolkit.builders.literal(isClass), instanceMemberRef, estreeToolkit.builders.literal(name))];
}

@@ -677,9 +729,16 @@ function reorderAttributes(attrs, props) {

const attrsAndProps = reorderAttributes(node.attributes, node.properties);
let hasClassAttribute = false;
const yieldAttrsAndProps = attrsAndProps.flatMap((attr) => {
const { name, value, type } = attr;
// For classes, these may need to be prefixed with the scope token
const isClass = type === 'Attribute' && name === 'class';
if (isClass) {
hasClassAttribute = true;
}
cxt.hoist(bImportHtmlEscape(), importHtmlEscapeKey);
if (attr.value.type === 'Literal') {
return yieldAttrOrPropLiteralValue(attr.name, attr.value);
if (value.type === 'Literal') {
return yieldAttrOrPropLiteralValue(name, value, isClass);
}
else {
return yieldAttrOrPropLiveValue(attr.name, attr.value);
return yieldAttrOrPropLiveValue(name, value, isClass);
}

@@ -692,2 +751,4 @@ });

bYield$1(estreeToolkit.builders.literal(`<${node.name}`)),
// If we haven't already prefixed the scope token to an existing class, add an explicit class here
...(hasClassAttribute ? [] : [bYield$1(estreeToolkit.builders.identifier('stylesheetScopeTokenClass'))]),
...yieldAttrsAndProps,

@@ -909,15 +970,19 @@ bYield$1(estreeToolkit.builders.literal(`>`)),

const bExportTemplate = esTemplate `
export default async function* tmpl(props, attrs, slotted, Cmp, instance, stylesheets) {
export default async function* tmpl(props, attrs, slotted, Cmp, instance) {
if (!${isBool} && Cmp.renderMode !== 'light') {
yield \`<template shadowrootmode="open"\${Cmp.delegatesFocus ? ' shadowrootdelegatesfocus' : ''}>\`
}
for (const stylesheet of stylesheets ?? []) {
// TODO
const token = null;
const useActualHostSelector = true;
const useNativeDirPseudoclass = null;
yield '<style type="text/css">';
yield stylesheet(token, useActualHostSelector, useNativeDirPseudoclass);
yield '</style>';
if (defaultStylesheets || defaultScopedStylesheets) {
// Flatten all stylesheets infinitely and concatenate
const stylesheets = [defaultStylesheets, defaultScopedStylesheets].filter(Boolean).flat(Infinity);
for (const stylesheet of stylesheets) {
const token = stylesheet.$scoped$ ? stylesheetScopeToken : undefined;
const useActualHostSelector = !stylesheet.$scoped$ || Cmp.renderMode !== 'light';
const useNativeDirPseudoclass = true;
yield '<style' + stylesheetScopeTokenClass + ' type="text/css">';
yield stylesheet(token, useActualHostSelector, useNativeDirPseudoclass);
yield '</style>';
}
}

@@ -928,7 +993,7 @@

if (!${isBool} && Cmp.renderMode !== 'light') {
yield '</template>'
yield '</template>';
}
}
`;
function compileTemplate(src, _filename) {
function compileTemplate(src, filename) {
const { root, warnings } = templateCompiler.parse(src);

@@ -952,2 +1017,5 @@ if (!root || warnings.length) {

const program = estreeToolkit.builders.program(moduleBody, 'module');
addScopeTokenDeclarations(program, filename);
const stylesheetImports = getStylesheetImports(filename);
program.body.unshift(...stylesheetImports);
return {

@@ -969,3 +1037,3 @@ code: astring.generate(program, {}),

function compileTemplateForSSR(src, filename, _options) {
const { code } = compileTemplate(src);
const { code } = compileTemplate(src, filename);
return { code, map: undefined };

@@ -976,3 +1044,3 @@ }

exports.compileTemplateForSSR = compileTemplateForSSR;
/** version: 8.1.0 */
/** version: 8.1.1 */
//# sourceMappingURL=index.cjs.js.map

@@ -10,3 +10,4 @@ /**

import { produce } from 'immer';
import { toPropertyName, kebabcaseToCamelcase, parse as parse$1 } from '@lwc/template-compiler';
import { generateScopeTokens, toPropertyName, kebabcaseToCamelcase, parse as parse$1 } from '@lwc/template-compiler';
import { builders as builders$1 } from 'estree-toolkit/dist/builders';
import { inspect } from 'util';

@@ -183,2 +184,5 @@

`;
const bDefaultScopedStyleImport = esTemplate `
import defaultScopedStylesheets from '${is.literal}';
`;
function catalogStyleImport(path, state) {

@@ -195,15 +199,14 @@ const specifier = path.node.specifiers[0];

}
const componentNamePattern = /(?<componentName>[^/]+)\.[tj]s$/;
/**
* This adds implicit style imports to the compiled component artifact.
*/
function addStylesheetImports(ast, state, filepath) {
const componentName = componentNamePattern.exec(filepath)?.groups?.componentName;
if (!componentName) {
throw new Error(`Could not determine component name from file path: ${filepath}`);
function getStylesheetImports(filepath) {
const moduleName = /(?<moduleName>[^/]+)\.html$/.exec(filepath)?.groups?.moduleName;
if (!moduleName) {
throw new Error(`Could not determine module name from file path: ${filepath}`);
}
if (state.cssExplicitImports || state.staticStylesheetIds) {
throw new Error(`Unimplemented static stylesheets, but found:\n${[...state.cssExplicitImports].join(' \n')}`);
}
ast.body.unshift(bDefaultStyleImport(builders.literal(`./${componentName}.css`)));
return [
bDefaultStyleImport(builders.literal(`./${moduleName}.css`)),
bDefaultScopedStyleImport(builders.literal(`./${moduleName}.scoped.css?scoped=true`)),
];
}

@@ -259,7 +262,8 @@ function catalogStaticStylesheets(ids, state) {

instance.connectedCallback?.();
const tmplFn = ${isIdentOrRenderCall} ?? __fallbackTmpl;
yield \`<\${tagName}\`;
yield tmplFn.stylesheetScopeTokenHostClass;
yield *__renderAttrs(attrs)
yield '>';
const tmplFn = ${isIdentOrRenderCall} ?? __fallbackTmpl;
yield* tmplFn(props, attrs, slotted, ${is.identifier}, instance, defaultStylesheets);
yield* tmplFn(props, attrs, slotted, ${is.identifier}, instance);
yield \`</\${tagName}>\`;

@@ -448,4 +452,6 @@ }

}
if (state.cssExplicitImports || state.staticStylesheetIds) {
throw new Error(`Unimplemented static stylesheets, but found:\n${[...state.cssExplicitImports].join(' \n')}`);
}
addGenerateMarkupExport(ast, state, filename);
addStylesheetImports(ast, state, filename);
return {

@@ -456,16 +462,40 @@ code: generate(ast, {}),

/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
const Comment = function Comment(node, cxt) {
if (cxt.templateOptions.preserveComments) {
return [builders.expressionStatement(builders.yieldExpression(builders.literal(`<!--${node.value}-->`)))];
}
else {
return [];
}
};
function generateStylesheetScopeToken(filename) {
// FIXME: we should be getting the namespace/name from the config options,
// since these actually come from the component filename, not the template filename.
const split = filename.split('/');
const namespace = split.at(-3);
const baseName = split.at(-1);
const componentName = baseName.replace(/\.[^.]+$/, '');
const {
// FIXME: handle legacy scope token for older API versions
scopeToken, } = generateScopeTokens(filename, namespace, componentName);
return scopeToken;
}
const bStylesheetTokenDeclaration = esTemplate `
const stylesheetScopeToken = '${is.literal}';
`;
const bAdditionalDeclarations = [
esTemplate `
const hasScopedStylesheets = defaultScopedStylesheets && defaultScopedStylesheets.length > 0;
`,
esTemplate `
const stylesheetScopeTokenClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}"\` : '';
`,
esTemplate `
const stylesheetScopeTokenHostClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}-host"\` : '';
`,
esTemplate `
const stylesheetScopeTokenClassPrefix = hasScopedStylesheets ? (stylesheetScopeToken + ' ') : '';
`,
];
// Scope tokens are associated with a given template. This is assigned here so that it can be used in `generateMarkup`.
const tmplAssignmentBlock = esTemplate `
${is.identifier}.stylesheetScopeTokenHostClass = stylesheetScopeTokenHostClass;
`;
function addScopeTokenDeclarations(program, filename) {
const scopeToken = generateStylesheetScopeToken(filename);
program.body.unshift(bStylesheetTokenDeclaration(builders$1.literal(scopeToken)), ...bAdditionalDeclarations.map((declaration) => declaration()));
program.body.push(tmplAssignmentBlock(builders$1.identifier('tmpl')));
}

@@ -530,2 +560,17 @@ /*

*/
const Comment = function Comment(node, cxt) {
if (cxt.templateOptions.preserveComments) {
return [builders.expressionStatement(builders.yieldExpression(builders.literal(`<!--${node.value}-->`)))];
}
else {
return [];
}
};
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
function getRootMemberExpression$1(node) {

@@ -621,2 +666,3 @@ return node.object.type === 'MemberExpression' ? getRootMemberExpression$1(node.object) : node;

{
const prefix = (${ /* isClass */is.literal} && stylesheetScopeTokenClassPrefix) || '';
const attrOrPropValue = ${is.expression};

@@ -627,3 +673,3 @@ const valueType = typeof attrOrPropValue;

if (valueType === 'string') {
yield '="' + htmlEscape(attrOrPropValue, true) + '"';
yield \`="\${prefix}\${htmlEscape(attrOrPropValue, true)}"\`;
}

@@ -633,7 +679,13 @@ }

`;
function yieldAttrOrPropLiteralValue(name, valueNode) {
const bStringLiteralYield = esTemplateWithYield `
{
const prefix = (${ /* isClass */is.literal} && stylesheetScopeTokenClassPrefix) || '';
yield ' ' + ${is.literal} + '="' + prefix + "${is.literal}" + '"'
}
`;
function yieldAttrOrPropLiteralValue(name, valueNode, isClass) {
const { value, type } = valueNode;
if (typeof value === 'string') {
const yieldedValue = name === 'style' ? cleanStyleAttrVal(value) : value;
return [bYield$1(builders.literal(` ${name}="${yieldedValue}"`))];
return [bStringLiteralYield(builders.literal(isClass), builders.literal(name), builders.literal(yieldedValue))];
}

@@ -645,5 +697,5 @@ else if (typeof value === 'boolean') {

}
function yieldAttrOrPropLiveValue(name, value) {
function yieldAttrOrPropLiveValue(name, value, isClass) {
const instanceMemberRef = builders.memberExpression(builders.identifier('instance'), value);
return [bConditionalLiveYield(instanceMemberRef, builders.literal(name))];
return [bConditionalLiveYield(builders.literal(isClass), instanceMemberRef, builders.literal(name))];
}

@@ -673,9 +725,16 @@ function reorderAttributes(attrs, props) {

const attrsAndProps = reorderAttributes(node.attributes, node.properties);
let hasClassAttribute = false;
const yieldAttrsAndProps = attrsAndProps.flatMap((attr) => {
const { name, value, type } = attr;
// For classes, these may need to be prefixed with the scope token
const isClass = type === 'Attribute' && name === 'class';
if (isClass) {
hasClassAttribute = true;
}
cxt.hoist(bImportHtmlEscape(), importHtmlEscapeKey);
if (attr.value.type === 'Literal') {
return yieldAttrOrPropLiteralValue(attr.name, attr.value);
if (value.type === 'Literal') {
return yieldAttrOrPropLiteralValue(name, value, isClass);
}
else {
return yieldAttrOrPropLiveValue(attr.name, attr.value);
return yieldAttrOrPropLiveValue(name, value, isClass);
}

@@ -688,2 +747,4 @@ });

bYield$1(builders.literal(`<${node.name}`)),
// If we haven't already prefixed the scope token to an existing class, add an explicit class here
...(hasClassAttribute ? [] : [bYield$1(builders.identifier('stylesheetScopeTokenClass'))]),
...yieldAttrsAndProps,

@@ -905,15 +966,19 @@ bYield$1(builders.literal(`>`)),

const bExportTemplate = esTemplate `
export default async function* tmpl(props, attrs, slotted, Cmp, instance, stylesheets) {
export default async function* tmpl(props, attrs, slotted, Cmp, instance) {
if (!${isBool} && Cmp.renderMode !== 'light') {
yield \`<template shadowrootmode="open"\${Cmp.delegatesFocus ? ' shadowrootdelegatesfocus' : ''}>\`
}
for (const stylesheet of stylesheets ?? []) {
// TODO
const token = null;
const useActualHostSelector = true;
const useNativeDirPseudoclass = null;
yield '<style type="text/css">';
yield stylesheet(token, useActualHostSelector, useNativeDirPseudoclass);
yield '</style>';
if (defaultStylesheets || defaultScopedStylesheets) {
// Flatten all stylesheets infinitely and concatenate
const stylesheets = [defaultStylesheets, defaultScopedStylesheets].filter(Boolean).flat(Infinity);
for (const stylesheet of stylesheets) {
const token = stylesheet.$scoped$ ? stylesheetScopeToken : undefined;
const useActualHostSelector = !stylesheet.$scoped$ || Cmp.renderMode !== 'light';
const useNativeDirPseudoclass = true;
yield '<style' + stylesheetScopeTokenClass + ' type="text/css">';
yield stylesheet(token, useActualHostSelector, useNativeDirPseudoclass);
yield '</style>';
}
}

@@ -924,7 +989,7 @@

if (!${isBool} && Cmp.renderMode !== 'light') {
yield '</template>'
yield '</template>';
}
}
`;
function compileTemplate(src, _filename) {
function compileTemplate(src, filename) {
const { root, warnings } = parse$1(src);

@@ -948,2 +1013,5 @@ if (!root || warnings.length) {

const program = builders.program(moduleBody, 'module');
addScopeTokenDeclarations(program, filename);
const stylesheetImports = getStylesheetImports(filename);
program.body.unshift(...stylesheetImports);
return {

@@ -965,3 +1033,3 @@ code: generate(program, {}),

function compileTemplateForSSR(src, filename, _options) {
const { code } = compileTemplate(src);
const { code } = compileTemplate(src, filename);
return { code, map: undefined };

@@ -971,3 +1039,3 @@ }

export { compileComponentForSSR, compileTemplateForSSR };
/** version: 8.1.0 */
/** version: 8.1.1 */
//# sourceMappingURL=index.js.map

@@ -7,3 +7,3 @@ {

"name": "@lwc/ssr-compiler",
"version": "8.1.0",
"version": "8.1.1",
"description": "Compile component for use during server-side rendering",

@@ -48,4 +48,4 @@ "keywords": [

"dependencies": {
"@lwc/shared": "8.1.0",
"@lwc/template-compiler": "8.1.0",
"@lwc/shared": "8.1.1",
"@lwc/template-compiler": "8.1.1",
"acorn": "8.12.1",

@@ -58,4 +58,4 @@ "astring": "^1.9.0",

"devDependencies": {
"@types/estree": "^1.0.5"
"@types/estree": "^1.0.6"
}
}

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