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

@codemirror/text

Package Overview
Dependencies
Maintainers
2
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@codemirror/text - npm Package Compare versions

Comparing version 0.18.0 to 0.18.1

10

CHANGELOG.md

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

## 0.18.1 (2021-07-06)
### Breaking changes
`findColumn` now takes only three arguments. (The old argument list will be supported until the next breaking version.)
## 0.18.0 (2021-03-03)
### Breaking changes
Update dependencies to 0.18.
## 0.17.2 (2021-01-06)

@@ -4,0 +14,0 @@

127

dist/index.d.ts

@@ -0,40 +1,159 @@

/**
Returns a next grapheme cluster break _after_ (not equal to)
`pos`, if `forward` is true, or before otherwise. Returns `pos`
itself if no further cluster break is available in the string.
Moves across surrogate pairs, extending characters, characters
joined with zero-width joiners, and flag emoji.
*/
declare function findClusterBreak(str: string, pos: number, forward?: boolean): number;
/**
Find the code point at the given position in a string (like the
[`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)
string method).
*/
declare function codePointAt(str: string, pos: number): number;
/**
Given a Unicode codepoint, return the JavaScript string that
respresents it (like
[`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)).
*/
declare function fromCodePoint(code: number): string;
/**
The first character that takes up two positions in a JavaScript
string. It is often useful to compare with this after calling
`codePointAt`, to figure out whether your character takes up 1 or
2 index positions.
*/
declare function codePointSize(code: number): 1 | 2;
/**
Count the column position at the given offset into the string,
taking extending characters and tab size into account.
*/
declare function countColumn(string: string, n: number, tabSize: number): number;
declare function findColumn(string: string, n: number, col: number, tabSize: number): {
offset: number;
leftOver: number;
};
/**
Find the offset that corresponds to the given column position in a
string, taking extending characters and tab size into account.
*/
declare function findColumn(string: string, col: number, tabSize: number): number;
/**
A text iterator iterates over a sequence of strings. When
iterating over a [`Text`](https://codemirror.net/6/docs/ref/#text.Text) document, result values will
either be lines or line breaks.
*/
interface TextIterator extends Iterator<string> {
/**
Retrieve the next string. Optionally skip a given number of
positions after the current position. Always returns the object
itself.
*/
next(skip?: number): this;
/**
The current string. Will be the empty string when the cursor is
at its end or `next` hasn't been called on it yet.
*/
value: string;
/**
Whether the end of the iteration has been reached. You should
probably check this right after calling `next`.
*/
done: boolean;
/**
Whether the current string represents a line break.
*/
lineBreak: boolean;
}
/**
The data structure for documents.
*/
declare abstract class Text implements Iterable<string> {
/**
The length of the string.
*/
abstract readonly length: number;
/**
The number of lines in the string (always >= 1).
*/
abstract readonly lines: number;
[Symbol.iterator]: () => Iterator<string>;
/**
Get the line description around the given position.
*/
lineAt(pos: number): Line;
/**
Get the description for the given (1-based) line number.
*/
line(n: number): Line;
/**
Replace a range of the text with the given content.
*/
replace(from: number, to: number, text: Text): Text;
/**
Append another document to this one.
*/
append(other: Text): Text;
/**
Retrieve the text between the given points.
*/
slice(from: number, to?: number): Text;
/**
Retrive a part of the document as a string
*/
abstract sliceString(from: number, to?: number, lineSep?: string): string;
/**
Test whether this text is equal to another instance.
*/
eq(other: Text): boolean;
/**
Iterate over the text. When `dir` is `-1`, iteration happens
from end to start. This will return lines and the breaks between
them as separate strings, and for long lines, might split lines
themselves into multiple chunks as well.
*/
iter(dir?: 1 | -1): TextIterator;
/**
Iterate over a range of the text. When `from` > `to`, the
iterator will run in reverse.
*/
iterRange(from: number, to?: number): TextIterator;
/**
Convert the document to an array of lines (which can be
deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#text.Text^of)).
*/
toJSON(): string[];
/**
Create a `Text` instance for the given array of lines.
*/
static of(text: readonly string[]): Text;
/**
The empty document.
*/
static empty: Text;
}
/**
This type describes a line in the document. It is created
on-demand when lines are [queried](https://codemirror.net/6/docs/ref/#text.Text.lineAt).
*/
declare class Line {
/**
The position of the start of the line.
*/
readonly from: number;
/**
The position at the end of the line (_before_ the line break,
or at the end of document for the last line).
*/
readonly to: number;
/**
This line's line number (1-based).
*/
readonly number: number;
/**
The line's content.
*/
readonly text: string;
/**
The length of the line (not including any line break after it).
*/
get length(): number;

@@ -41,0 +160,0 @@ }

264

dist/index.js

@@ -7,3 +7,3 @@ // Compressed representation of the Grapheme_Cluster_Break=Extend

// string being a shorthand for 1.
let extend = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(s => s ? parseInt(s, 36) : 1);
let extend = /*@__PURE__*/"lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(s => s ? parseInt(s, 36) : 1);
// Convert offsets into absolute values

@@ -22,7 +22,9 @@ for (let i = 1; i < extend.length; i++)

const ZWJ = 0x200d;
/// Returns a next grapheme cluster break _after_ (not equal to)
/// `pos`, if `forward` is true, or before otherwise. Returns `pos`
/// itself if no further cluster break is available in the string.
/// Moves across surrogate pairs, extending characters, characters
/// joined with zero-width joiners, and flag emoji.
/**
Returns a next grapheme cluster break _after_ (not equal to)
`pos`, if `forward` is true, or before otherwise. Returns `pos`
itself if no further cluster break is available in the string.
Moves across surrogate pairs, extending characters, characters
joined with zero-width joiners, and flag emoji.
*/
function findClusterBreak(str, pos, forward = true) {

@@ -73,5 +75,7 @@ return (forward ? nextClusterBreak : prevClusterBreak)(str, pos);

function surrogateHigh(ch) { return ch >= 0xD800 && ch < 0xDC00; }
/// Find the code point at the given position in a string (like the
/// [`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)
/// string method).
/**
Find the code point at the given position in a string (like the
[`codePointAt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)
string method).
*/
function codePointAt(str, pos) {

@@ -86,5 +90,7 @@ let code0 = str.charCodeAt(pos);

}
/// Given a Unicode codepoint, return the JavaScript string that
/// respresents it (like
/// [`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)).
/**
Given a Unicode codepoint, return the JavaScript string that
respresents it (like
[`String.fromCodePoint`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint)).
*/
function fromCodePoint(code) {

@@ -96,10 +102,14 @@ if (code <= 0xffff)

}
/// The first character that takes up two positions in a JavaScript
/// string. It is often useful to compare with this after calling
/// `codePointAt`, to figure out whether your character takes up 1 or
/// 2 index positions.
/**
The first character that takes up two positions in a JavaScript
string. It is often useful to compare with this after calling
`codePointAt`, to figure out whether your character takes up 1 or
2 index positions.
*/
function codePointSize(code) { return code < 0x10000 ? 1 : 2; }
/// Count the column position at the given offset into the string,
/// taking extending characters and tab size into account.
/**
Count the column position at the given offset into the string,
taking extending characters and tab size into account.
*/
function countColumn(string, n, tabSize) {

@@ -118,19 +128,32 @@ for (let i = 0; i < string.length;) {

}
/// Find the offset that corresponds to the given column position in a
/// string, taking extending characters and tab size into account.
function findColumn(string, n, col, tabSize) {
for (let i = 0; i < string.length;) {
/**
Find the offset that corresponds to the given column position in a
string, taking extending characters and tab size into account.
*/
function findColumn(string, col, tabSize) {
let _compat = arguments[3];
if (_compat != null) {
col = tabSize;
tabSize = _compat;
} // FIXME remove at next major version
for (let i = 0, n = 0; i < string.length;) {
if (n >= col)
return { offset: i, leftOver: 0 };
return i;
n += string.charCodeAt(i) == 9 ? tabSize - (n % tabSize) : 1;
i = findClusterBreak(string, i);
}
return { offset: string.length, leftOver: col - n };
return string.length;
}
/// The data structure for documents.
/**
The data structure for documents.
*/
class Text {
/// @internal
/**
@internal
*/
constructor() { }
/// Get the line description around the given position.
/**
Get the line description around the given position.
*/
lineAt(pos) {

@@ -141,3 +164,5 @@ if (pos < 0 || pos > this.length)

}
/// Get the description for the given (1-based) line number.
/**
Get the description for the given (1-based) line number.
*/
line(n) {

@@ -148,3 +173,5 @@ if (n < 1 || n > this.lines)

}
/// Replace a range of the text with the given content.
/**
Replace a range of the text with the given content.
*/
replace(from, to, text) {

@@ -158,7 +185,11 @@ let parts = [];

}
/// Append another document to this one.
/**
Append another document to this one.
*/
append(other) {
return this.replace(this.length, this.length, other);
}
/// Retrieve the text between the given points.
/**
Retrieve the text between the given points.
*/
slice(from, to = this.length) {

@@ -169,3 +200,5 @@ let parts = [];

}
/// Test whether this text is equal to another instance.
/**
Test whether this text is equal to another instance.
*/
eq(other) {

@@ -186,14 +219,22 @@ if (other == this)

}
/// Iterate over the text. When `dir` is `-1`, iteration happens
/// from end to start. This will return lines and the breaks between
/// them as separate strings, and for long lines, might split lines
/// themselves into multiple chunks as well.
/**
Iterate over the text. When `dir` is `-1`, iteration happens
from end to start. This will return lines and the breaks between
them as separate strings, and for long lines, might split lines
themselves into multiple chunks as well.
*/
iter(dir = 1) { return new RawTextCursor(this, dir); }
/// Iterate over a range of the text. When `from` > `to`, the
/// iterator will run in reverse.
/**
Iterate over a range of the text. When `from` > `to`, the
iterator will run in reverse.
*/
iterRange(from, to = this.length) { return new PartialTextCursor(this, from, to); }
/// @internal
/**
@internal
*/
toString() { return this.sliceString(0); }
/// Convert the document to an array of lines (which can be
/// deserialized again via [`Text.of`](#text.Text^of)).
/**
Convert the document to an array of lines (which can be
deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#text.Text^of)).
*/
toJSON() {

@@ -204,3 +245,5 @@ let lines = [];

}
/// Create a `Text` instance for the given array of lines.
/**
Create a `Text` instance for the given array of lines.
*/
static of(text) {

@@ -418,3 +461,3 @@ if (text.length == 0)

}
Text.empty = new TextLeaf([""], 0);
Text.empty = /*@__PURE__*/new TextLeaf([""], 0);
function textLength(text) {

@@ -455,24 +498,25 @@ let length = -1;

this.nodes = [text];
this.offsets = [dir > 0 ? 0 : text instanceof TextLeaf ? text.text.length : text.children.length];
this.offsets = [dir > 0 ? 1 : (text instanceof TextLeaf ? text.text.length : text.children.length) << 1];
}
next(skip = 0) {
nextInner(skip, dir) {
this.done = this.lineBreak = false;
for (;;) {
let last = this.nodes.length - 1;
if (last < 0) {
this.done = true;
this.value = "";
this.lineBreak = false;
return this;
}
let top = this.nodes[last], offset = this.offsets[last];
let top = this.nodes[last], offsetValue = this.offsets[last], offset = offsetValue >> 1;
let size = top instanceof TextLeaf ? top.text.length : top.children.length;
if (offset == (this.dir > 0 ? size : 0)) {
if (offset == (dir > 0 ? size : 0)) {
if (last == 0) {
this.done = true;
this.value = "";
return this;
}
if (dir > 0)
this.offsets[last - 1]++;
this.nodes.pop();
this.offsets.pop();
}
else if (!this.lineBreak && offset != (this.dir > 0 ? 0 : size)) {
// Internal offset with lineBreak == false means we have to
// count the line break at this position
this.lineBreak = true;
else if ((offsetValue & 1) == (dir > 0 ? 0 : 1)) {
this.offsets[last] += dir;
if (skip == 0) {
this.lineBreak = true;
this.value = "\n";

@@ -485,7 +529,6 @@ return this;

// Move to the next string
let next = top.text[offset - (this.dir < 0 ? 1 : 0)];
this.offsets[last] = (offset += this.dir);
this.lineBreak = false;
let next = top.text[offset + (dir < 0 ? -1 : 0)];
this.offsets[last] += dir;
if (next.length > Math.max(0, skip)) {
this.value = skip == 0 ? next : this.dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip);
this.value = skip == 0 ? next : dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip);
return this;

@@ -496,11 +539,12 @@ }

else {
let next = top.children[this.dir > 0 ? offset : offset - 1];
this.offsets[last] = offset + this.dir;
this.lineBreak = false;
let next = top.children[offset + (dir < 0 ? -1 : 0)];
if (skip > next.length) {
skip -= next.length;
this.offsets[last] += dir;
}
else {
if (dir < 0)
this.offsets[last]--;
this.nodes.push(next);
this.offsets.push(this.dir > 0 ? 0 : next instanceof TextLeaf ? next.text.length : next.children.length);
this.offsets.push(dir > 0 ? 1 : (next instanceof TextLeaf ? next.text.length : next.children.length) << 1);
}

@@ -510,2 +554,9 @@ }

}
next(skip = 0) {
if (skip < 0) {
this.nextInner(-skip, (-this.dir));
skip = this.value.length;
}
return this.nextInner(skip, this.dir);
}
}

@@ -515,46 +566,59 @@ class PartialTextCursor {

this.value = "";
this.done = false;
this.cursor = new RawTextCursor(text, start > end ? -1 : 1);
if (start > end) {
this.skip = text.length - start;
this.limit = start - end;
}
else {
this.skip = start;
this.limit = end - start;
}
this.pos = start > end ? text.length : 0;
this.from = Math.min(start, end);
this.to = Math.max(start, end);
}
next(skip = 0) {
if (this.limit <= 0) {
this.limit = -1;
nextInner(skip, dir) {
if (dir < 0 ? this.pos <= this.from : this.pos >= this.to) {
this.value = "";
this.done = true;
return this;
}
else {
let { value, lineBreak, done } = this.cursor.next(this.skip + skip);
this.skip = 0;
this.value = value;
let len = lineBreak ? 1 : value.length;
if (len > this.limit)
this.value = this.cursor.dir > 0 ? value.slice(0, this.limit) : value.slice(len - this.limit);
if (done || this.value.length == 0)
this.limit = -1;
else
this.limit -= this.value.length;
}
this.done = false;
skip += Math.max(0, dir < 0 ? this.pos - this.to : this.from - this.pos);
let limit = dir < 0 ? this.pos - this.from : this.to - this.pos;
if (skip > limit)
skip = limit;
limit -= skip;
let { value } = this.cursor.next(skip);
this.pos += (value.length + skip) * dir;
this.value = value.length <= limit ? value : dir < 0 ? value.slice(value.length - limit) : value.slice(0, limit);
return this;
}
get lineBreak() { return this.cursor.lineBreak; }
get done() { return this.limit < 0; }
next(skip = 0) {
if (skip < 0)
skip = Math.max(skip, this.from - this.pos);
else if (skip > 0)
skip = Math.min(skip, this.to - this.pos);
return this.nextInner(skip, this.cursor.dir);
}
get lineBreak() { return this.cursor.lineBreak && this.value != ""; }
}
/// This type describes a line in the document. It is created
/// on-demand when lines are [queried](#text.Text.lineAt).
/**
This type describes a line in the document. It is created
on-demand when lines are [queried](https://codemirror.net/6/docs/ref/#text.Text.lineAt).
*/
class Line {
/// @internal
/**
@internal
*/
constructor(
/// The position of the start of the line.
/**
The position of the start of the line.
*/
from,
/// The position at the end of the line (_before_ the line break,
/// or at the end of document for the last line).
/**
The position at the end of the line (_before_ the line break,
or at the end of document for the last line).
*/
to,
/// This line's line number (1-based).
/**
This line's line number (1-based).
*/
number,
/// The line's content.
/**
The line's content.
*/
text) {

@@ -566,3 +630,5 @@ this.from = from;

}
/// The length of the line (not including any line break after it).
/**
The length of the line (not including any line break after it).
*/
get length() { return this.to - this.from; }

@@ -569,0 +635,0 @@ }

{
"name": "@codemirror/text",
"version": "0.18.0",
"version": "0.18.1",
"description": "Document data structure for the CodeMirror code editor",
"scripts": {
"test": "mocha test/test-*.js",
"prepare": "tsc -p tsconfig.local.json && rollup -c"
"test": "cm-runtests",
"prepare": "cm-buildhelper src/index.ts"
},

@@ -30,8 +30,3 @@ "keywords": [

"devDependencies": {
"rollup": "^2.35.1",
"rollup-plugin-dts": "^2.0.1",
"typescript": "^4.1.3",
"@types/mocha": "^5.2.0",
"ist": "^1.1.6",
"mocha": "^7.1.1"
"@codemirror/buildhelper": "^0.1.5"
},

@@ -38,0 +33,0 @@ "repository": {

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