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

terra-alert

Package Overview
Dependencies
Maintainers
8
Versions
182
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

terra-alert - npm Package Compare versions

Comparing version 4.67.0 to 4.68.0

7

CHANGELOG.md

@@ -5,2 +5,9 @@ # Changelog

## 4.68.0 - (May 9, 2023)
* Changed
* Added screen reader support to programmatically associate an alert's text to its dismiss button.
* Added text-wrapping at high magnification and narrow screen widths.
* Changed Alert role attribute with default of "status" for all types except critical alert type and added new optional `role` prop.
## 4.67.0 - (April 27, 2023)

@@ -7,0 +14,0 @@

19

lib/Alert.js

@@ -23,4 +23,5 @@ "use strict";

var _terraThemeContext = _interopRequireDefault(require("terra-theme-context"));
var _uuid = require("uuid");
var _AlertModule = _interopRequireDefault(require("./Alert.module.scss"));
var _excluded = ["action", "children", "customIcon", "customColorClass", "onDismiss", "intl", "title", "type"];
var _excluded = ["action", "children", "customIcon", "customColorClass", "onDismiss", "intl", "role", "title", "type"];
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -86,2 +87,7 @@ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

/**
* The ARIA role attribute of the alert. If not provided, alert type _alert_ will default to role `"alert"`,
* all other alert types will use the role of `"status"`.
*/
role: _propTypes.default.string,
/**
* The title for the alert which will be bolded.

@@ -147,2 +153,3 @@ */

intl = _ref.intl,
role = _ref.role,
title = _ref.title,

@@ -159,2 +166,3 @@ type = _ref.type,

});
var defaultRole = type === AlertTypes.ALERT ? 'alert' : 'status';
var alertClassNames = (0, _classnames.default)(cx('alert-base', type, {

@@ -170,5 +178,10 @@ narrow: isNarrow

});
var alertId = (0, _uuid.v4)();
var alertTitleId = "alert-title-".concat(alertId);
var alertMessageId = "alert-message-".concat(alertId);
var dismissButtonAriaDescribedBy = title || defaultTitle ? alertTitleId : alertMessageId;
var dismissButton;
if (onDismiss) {
dismissButton = /*#__PURE__*/_react.default.createElement(_terraButton.default, {
"aria-describedby": dismissButtonAriaDescribedBy,
text: intl.formatMessage({

@@ -193,4 +206,6 @@ id: 'Terra.alert.dismiss'

var alertMessageContent = /*#__PURE__*/_react.default.createElement("div", {
id: alertMessageId,
className: alertSectionClassName
}, (title || defaultTitle) && /*#__PURE__*/_react.default.createElement("strong", {
id: alertTitleId,
className: cx('title')

@@ -206,3 +221,3 @@ }, title || defaultTitle), children);

}, /*#__PURE__*/_react.default.createElement("div", _extends({
role: "alert"
role: role || defaultRole
}, customProps, {

@@ -209,0 +224,0 @@ className: alertClassNames

7

package.json
{
"name": "terra-alert",
"main": "lib/Alert.js",
"version": "4.67.0",
"version": "4.68.0",
"description": "The Terra Alert component is a notification banner that can be rendered in your application when there is information that you want to bring to the user's attention. The Alert component supports a number of built-in notification types that render with pre-defined colors and icons that help the user understand the severity and meaning of the notification. A custom notification type is also supported that allows your application to customize an alert that may not fit into the pre-defined types.",

@@ -35,3 +35,4 @@ "repository": {

"terra-responsive-element": "^5.37.0",
"terra-theme-context": "^1.0.0"
"terra-theme-context": "^1.0.0",
"uuid": "^9.0.0"
},

@@ -51,3 +52,3 @@ "scripts": {

},
"gitHead": "78e099e7db5bee2cff914c34c00278faffd86d44"
"gitHead": "530ec2566113aae6bfe1ce27b6160a910d180bb7"
}

@@ -16,2 +16,3 @@ import React, { useState } from 'react';

import ThemeContext from 'terra-theme-context';
import { v4 as uuidv4 } from 'uuid';

@@ -65,2 +66,7 @@ import styles from './Alert.module.scss';

/**
* The ARIA role attribute of the alert. If not provided, alert type _alert_ will default to role `"alert"`,
* all other alert types will use the role of `"status"`.
*/
role: PropTypes.string,
/**
* The title for the alert which will be bolded.

@@ -123,2 +129,3 @@ */

intl,
role,
title,

@@ -132,2 +139,3 @@ type,

const defaultTitle = type === AlertTypes.CUSTOM ? '' : intl.formatMessage({ id: `Terra.alert.${type}` });
const defaultRole = type === AlertTypes.ALERT ? 'alert' : 'status';
const alertClassNames = classNames(

@@ -151,5 +159,17 @@ cx(

const alertId = uuidv4();
const alertTitleId = `alert-title-${alertId}`;
const alertMessageId = `alert-message-${alertId}`;
const dismissButtonAriaDescribedBy = (title || defaultTitle) ? alertTitleId : alertMessageId;
let dismissButton;
if (onDismiss) {
dismissButton = <Button text={intl.formatMessage({ id: 'Terra.alert.dismiss' })} onClick={onDismiss} />;
dismissButton = (
<Button
aria-describedby={dismissButtonAriaDescribedBy}
text={intl.formatMessage({ id: 'Terra.alert.dismiss' })}
onClick={onDismiss}
/>
);
}

@@ -168,6 +188,12 @@

const alertSectionClassName = cx('section', { 'section-custom': type === AlertTypes.CUSTOM });
const alertSectionClassName = cx('section', {
'section-custom': type === AlertTypes.CUSTOM,
});
const alertMessageContent = (
<div className={alertSectionClassName}>
{(title || defaultTitle) && <strong className={cx('title')}>{title || defaultTitle}</strong>}
<div id={alertMessageId} className={alertSectionClassName}>
{(title || defaultTitle) && (
<strong id={alertTitleId} className={cx('title')}>
{title || defaultTitle}
</strong>
)}
{children}

@@ -186,3 +212,3 @@ </div>

>
<div role="alert" {...customProps} className={alertClassNames}>
<div role={role || defaultRole} {...customProps} className={alertClassNames}>
<div className={bodyClassNameForParent}>

@@ -195,3 +221,2 @@ {getAlertIcon(type, customIcon)}

</ResponsiveElement>
);

@@ -198,0 +223,0 @@ };

import React from 'react';
/* eslint-disable-next-line import/no-extraneous-dependencies */
import { shallowWithIntl, mountWithIntl } from 'terra-enzyme-intl';
import IconAlert from 'terra-icon/lib/icon/IconAlert';
import IconDiamondSymbol from 'terra-icon/lib/icon/IconDiamondSymbol';
import IconError from 'terra-icon/lib/icon/IconError';
import IconGapChecking from 'terra-icon/lib/icon/IconGapChecking';
import IconHelp from 'terra-icon/lib/icon/IconHelp';
import IconInformation from 'terra-icon/lib/icon/IconInformation';
import IconSuccess from 'terra-icon/lib/icon/IconSuccess';
import IconWarning from 'terra-icon/lib/icon/IconWarning';
import Button from 'terra-button';
import Alert from '../../src/Alert';
const mockUUID = '00000000-0000-0000-0000-000000000000';
jest.mock('uuid', () => ({ v4: () => mockUUID }));
describe('Alert with no props', () => {
// Snapshot Tests
it('should render a default component', () => {

@@ -16,6 +25,24 @@ const wrapper = mountWithIntl(<Alert />);

describe('Alert with role prop', () => {
it('should render a alert with provided role', () => {
const wrapper = shallowWithIntl(<Alert role="status" />).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base alert wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconAlert).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.alert');
expect(wrapper).toMatchSnapshot();
});
});
describe('Dismissible Alert that includes actions section', () => {
// Snapshot Tests
it('should render an alert component with a dismiss button', () => {
const wrapper = mountWithIntl(<Alert onDismiss={() => { }}>This is a test</Alert>);
const mockOnDismiss = jest.fn();
const wrapper = shallowWithIntl(<Alert onDismiss={mockOnDismiss}>This is a test</Alert>).dive();
expect(wrapper.find(Button).length).toEqual(1);
expect(wrapper.find(Button).prop('text')).toEqual('Terra.alert.dismiss');
expect(wrapper.find(Button).prop('onClick')).toEqual(mockOnDismiss);
expect(wrapper.find(Button).prop('variant')).toEqual('neutral');
expect(wrapper).toMatchSnapshot();

@@ -26,5 +53,10 @@ });

describe('Alert of type alert with text content', () => {
// Snapshot Tests
it('should render an Alert component of type alert', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.ALERT}>This is a test</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.ALERT}>This is a test</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base alert wide');
expect(alertDiv.prop('role')).toEqual('alert');
expect(wrapper.find(IconAlert).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.alert');
expect(wrapper).toMatchSnapshot();

@@ -35,5 +67,10 @@ });

describe('Alert of type error with text content', () => {
// Snapshot Tests
it('should render an Alert component of type error', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.ERROR}>This is an error.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.ERROR}>This is an error.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base error wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconError).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.error');
expect(wrapper).toMatchSnapshot();

@@ -44,5 +81,10 @@ });

describe('Alert of type warning with text content', () => {
// Snapshot Tests
it('should render an Alert component of type warning', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.WARNING}>This is an warning.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.WARNING}>This is an warning.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base warning wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconWarning).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.warning');
expect(wrapper).toMatchSnapshot();

@@ -53,5 +95,9 @@ });

describe('Alert of type advisory with text content', () => {
// Snapshot Tests
it('should render an Alert component of type advisory', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.ADVISORY}>This is an advisory alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.ADVISORY}>This is an advisory alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base advisory wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find('.title').text()).toEqual('Terra.alert.advisory');
expect(wrapper).toMatchSnapshot();

@@ -63,3 +109,9 @@ });

it('should render an unsatisfied Alert', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.UNSATISFIED}>This is an unsatisfied alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.UNSATISFIED}>This is an unsatisfied alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base unsatisfied wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconGapChecking).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.unsatisfied');
expect(wrapper).toMatchSnapshot();

@@ -71,3 +123,9 @@ });

it('should render an unverified Alert', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.UNVERIFIED}>This is an unverified alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.UNVERIFIED}>This is an unverified alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base unverified wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconDiamondSymbol).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.unverified');
expect(wrapper).toMatchSnapshot();

@@ -78,5 +136,10 @@ });

describe('Alert of type info with text content', () => {
// Snapshot Tests
it('should render an Alert component of type info', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.INFO}>This is an information alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.INFO}>This is an information alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base info wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconInformation).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.info');
expect(wrapper).toMatchSnapshot();

@@ -87,5 +150,10 @@ });

describe('Alert of type success with text content', () => {
// Snapshot Tests
it('should render an Alert component of type success', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.SUCCESS}>This is a success alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.SUCCESS}>This is a success alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base success wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconSuccess).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.success');
expect(wrapper).toMatchSnapshot();

@@ -96,5 +164,10 @@ });

describe('Alert of type custom with custom title and text content', () => {
// Snapshot Tests
it('should render an Alert component of type custom', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.CUSTOM} title="Help!" customIcon={<IconHelp />} customColorClass="terra-alert-custom-orange-color">This is a custom alert.</Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.CUSTOM} title="Help!" customIcon={<IconHelp />} customColorClass="terra-alert-custom-orange-color">This is a custom alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base custom wide terra-alert-custom-orange-color');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconHelp).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Help!');
expect(wrapper).toMatchSnapshot();

@@ -105,5 +178,8 @@ });

describe('Alert of type info with custom title and HTML content', () => {
// Snapshot Tests
it('should render an Alert component of type info with custom title and HTML content', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.INFO} title="Gettysburg Address"><span>Four score and seven years ago . . .</span></Alert>);
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.INFO} title="Gettysburg Address"><span>Four score and seven years ago . . .</span></Alert>);
expect(wrapper.prop('title')).toEqual('Gettysburg Address');
expect(wrapper.prop('type')).toEqual('info');
expect(wrapper.find('span').text()).toEqual('Four score and seven years ago . . .');
expect(wrapper).toMatchSnapshot();

@@ -114,5 +190,15 @@ });

describe('Alert of type success with an action button text content', () => {
// Snapshot Tests
it('should render an Alert component of type success with an action button', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.SUCCESS} action={<Button text="Action" variant={Button.Opts.Variants.EMPHASIS} onClick={() => { }} />}>This is a success alert.</Alert>);
const mockOnClick = jest.fn();
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.SUCCESS} action={<Button text="Action" variant={Button.Opts.Variants.EMPHASIS} onClick={mockOnClick} />}>This is a success alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base success wide');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconSuccess).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Terra.alert.success');
expect(wrapper.find(Button).length).toEqual(1);
expect(wrapper.find(Button).prop('text')).toEqual('Action');
expect(wrapper.find(Button).prop('onClick')).toEqual(mockOnClick);
expect(wrapper.find(Button).prop('variant')).toEqual('emphasis');
expect(wrapper).toMatchSnapshot();

@@ -123,5 +209,22 @@ });

describe('Dismissable Alert of type custom with action button, custom title and text content', () => {
// Snapshot Tests
it('should render an Alert component of type custom with an action button', () => {
const wrapper = mountWithIntl(<Alert type={Alert.Opts.Types.CUSTOM} onDismiss={() => { }} title="Help!" customIcon={<IconHelp />} customColorClass="terra-alert-custom-orange-color" action={<Button text="Action" variant={Button.Opts.Variants.EMPHASIS} onClick={() => { }} />}>This is a custom alert.</Alert>);
const mockOnClick = jest.fn();
const mockOnDismiss = jest.fn();
const wrapper = shallowWithIntl(<Alert type={Alert.Opts.Types.CUSTOM} onDismiss={mockOnDismiss} title="Help!" customIcon={<IconHelp />} customColorClass="terra-alert-custom-orange-color" action={<Button text="Action" variant={Button.Opts.Variants.EMPHASIS} onClick={mockOnClick} />}>This is a custom alert.</Alert>).dive();
const alertDiv = wrapper.find('div.alert-base');
expect(alertDiv.prop('className')).toEqual('alert-base custom wide terra-alert-custom-orange-color');
expect(alertDiv.prop('role')).toEqual('status');
expect(wrapper.find(IconHelp).length).toEqual(1);
expect(wrapper.find('.title').text()).toEqual('Help!');
const buttons = wrapper.find(Button);
expect(buttons.length).toEqual(2);
// action button
expect(buttons.at(0).prop('text')).toEqual('Action');
expect(buttons.at(0).prop('onClick')).toEqual(mockOnClick);
expect(buttons.at(0).prop('variant')).toEqual('emphasis');
// dismiss button
expect(buttons.at(1).prop('text')).toEqual('Terra.alert.dismiss');
expect(buttons.at(1).prop('onClick')).toEqual(mockOnDismiss);
expect(buttons.at(1).prop('variant')).toEqual('neutral');
expect(wrapper).toMatchSnapshot();

@@ -131,2 +234,121 @@ });

describe('Dismissible Alert', () => {
let wrapper;
describe('Custom Alert with no title prop', () => {
beforeEach(() => {
wrapper = shallowWithIntl(
<Alert
type={Alert.Opts.Types.CUSTOM}
onDismiss={() => { }}
customIcon={<IconHelp />}
>
This is a custom alert.
</Alert>,
).dive();
});
it('should set the alert message ID', () => {
const alertContent = wrapper.find('.section');
expect(alertContent.prop('id')).toEqual(`alert-message-${mockUUID}`);
});
it('should set the dismiss button aria-describedby to the alert description', () => {
const dismissButton = wrapper.find('Button');
expect(dismissButton.prop('aria-describedby')).toEqual(`alert-message-${mockUUID}`);
});
});
describe('Custom Alert with custom title', () => {
beforeEach(() => {
wrapper = shallowWithIntl(
<Alert
type={Alert.Opts.Types.CUSTOM}
title="Help!"
onDismiss={() => { }}
customIcon={<IconHelp />}
>
This is a custom alert.
</Alert>,
).dive();
});
it('should set the alert message ID', () => {
const alertContent = wrapper.find('.section');
expect(alertContent.prop('id')).toEqual(`alert-message-${mockUUID}`);
});
it('should set the alert title ID', () => {
const alertTitle = wrapper.find('.title');
expect(alertTitle.prop('id')).toEqual(`alert-title-${mockUUID}`);
});
it('should set the dismiss button aria-describedby to the alert title', () => {
const dismissButton = wrapper.find('Button');
expect(dismissButton.prop('aria-describedby')).toEqual(`alert-title-${mockUUID}`);
});
});
describe('Success Alert with no title prop', () => {
beforeEach(() => {
wrapper = shallowWithIntl(
<Alert
type={Alert.Opts.Types.SUCCESS}
onDismiss={() => { }}
>
This is a success alert.
</Alert>,
).dive();
});
it('should set the alert message ID', () => {
const alertContent = wrapper.find('.section');
expect(alertContent.prop('id')).toEqual(`alert-message-${mockUUID}`);
});
it('should set the alert title ID', () => {
const alertTitle = wrapper.find('.title');
expect(alertTitle.prop('id')).toEqual(`alert-title-${mockUUID}`);
});
it('should set the dismiss button aria-describedby to the alert title', () => {
const dismissButton = wrapper.find('Button');
expect(dismissButton.prop('aria-describedby')).toEqual(`alert-title-${mockUUID}`);
});
});
describe('Success Alert with blank title', () => {
beforeEach(() => {
wrapper = shallowWithIntl(
<Alert
type={Alert.Opts.Types.SUCCESS}
title=""
onDismiss={() => { }}
>
This is a success alert.
</Alert>,
).dive();
});
it('should use the default title', () => {
const alertTitle = wrapper.find('.title');
expect(alertTitle.prop('children')).toEqual('Terra.alert.success');
});
it('should set the alert message ID', () => {
const alertContent = wrapper.find('.section');
expect(alertContent.prop('id')).toEqual(`alert-message-${mockUUID}`);
});
it('should set the alert title ID', () => {
const alertTitle = wrapper.find('.title');
expect(alertTitle.prop('id')).toEqual(`alert-title-${mockUUID}`);
});
it('should set the dismiss button aria-describedby to the alert title', () => {
const dismissButton = wrapper.find('Button');
expect(dismissButton.prop('aria-describedby')).toEqual(`alert-title-${mockUUID}`);
});
});
});
it('correctly applies the theme context className', () => {

@@ -133,0 +355,0 @@ jest.spyOn(React, 'useContext')

@@ -63,2 +63,11 @@ Terra.describeViewports('Alert', ['tiny', 'large'], () => {

});
describe('Alert Long Text', () => {
it('wraps naturally at high magnification', () => {
browser.setWindowRect(0, 0, 100, 800);
browser.url('/raw/tests/cerner-terra-core-docs/alert/long-text-alert');
Terra.validates.element('text wrapping');
});
});
});

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