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

@livepeer/core

Package Overview
Dependencies
Maintainers
6
Versions
126
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@livepeer/core - npm Package Compare versions

Comparing version 3.0.0 to 3.0.1

147

dist/cjs/external.d.ts

@@ -135,19 +135,48 @@ type MimeType = keyof typeof mime;

*
* This function is designed to handle multiple input types: `PlaybackInfo`, `Source`, `string[]`, and `string`.
* This function is designed to handle multiple input types: Livepeer playback info, Cloudflare stream data, Mux URLs, `string[]`, and `string`.
* It processes these inputs to extract or construct source objects and then transforms these into `Src` inputs.
*
* - `PlaybackInfo`: Extracts the 'source' array from its 'meta' property.
* - `Source`: Directly uses the Source object.
* These include the video playback information, as well as supporting data like thumbnails and VTT files, which can be used by the Player.
*
* The input types and their processing are as follows:
* - `LivepeerPlaybackInfo`: Extracts the 'source' array from its 'meta' property.
* - `LivepeerSource` or `LivepeerSource[]`: Uses the source object(s) directly.
* - `CloudflareStreamData`: Retrieves the stream data and constructs Source objects.
* - `CloudflareUrlData`: Uses the URL data to create a Source object.
* - `string[]`: Assumes each string as a URL and creates an array of Source objects.
* - `string`: Assumes the string is a URL and creates a single Source object.
*
* @param {PlaybackInfo | Source | string[] | string | null | undefined} playbackInfo - The playback information to be parsed.
* It can be of type `PlaybackInfo`, `Source`, `string[]` of URLs, or a single URL.
* @returns {Src[] | null} An array of `Src` derived from the provided playback information, or null if the input is invalid or empty.
* @param {LivepeerPlaybackInfo | LivepeerSource | LivepeerSource[] | CloudflareStreamData | CloudflareUrlData | string[] | string | null | undefined} source - The playback information to be parsed.
* It can be of type `LivepeerPlaybackInfo`, `LivepeerSource`, `LivepeerSource[]`, `CloudflareStreamData`, `CloudflareUrlData`, `string[]` of URLs, or a single URL.
* @returns {Src[] | null} An array of `Src` objects derived from the provided playback information, or null if the input is invalid or empty.
*
* Each `Src` object may contain the following properties:
* - `url`: The URL of the media source.
* - `size`: The size of the media file (optional).
* - `width`: The width of the media (optional).
* - `height`: The height of the media (optional).
* - `bitrate`: The bitrate of the media (optional).
*/
declare const getSrc: (source: PlaybackInfo | Source | string[] | string | null | undefined) => Src[] | null;
declare const getSrc: (source: LivepeerPlaybackInfo | LivepeerSource | LivepeerSource[] | CloudflareStreamData | CloudflareUrlData | string[] | string | null | undefined) => Src[] | null;
/**
* Parses various types of ingest information and converts them into a WHIP URL.
*
* This function is designed to handle multiple input types: strings (assumed to be stream keys or URLs), Cloudflare stream data, Cloudflare URL data, or Livepeer stream data. It processes these inputs to either return the URL directly, construct a WHIP URL using the provided base URL, or extract the URL from object data.
*
* - If the input is a valid URL (starting with http/https), it returns the URL directly.
* - If the input is a string not starting with http/https, it treats the string as a stream key and constructs a WHIP URL using the provided base URL from opts.
* - For object inputs (`CloudflareStreamData`, `CloudflareUrlData`, or `LivepeerStream`), it attempts to extract the URL from the `url`, `webRTC.url`, or `streamKey` properties.
*
* @param {string | LivepeerStream | CloudflareStreamData | CloudflareUrlData | null | undefined} ingest - The ingest information to be parsed. It can be a stream key, URL, Cloudflare stream data, Cloudflare URL data, or Livepeer stream data.
* @param {Object} [opts] - Optional parameters.
* @param {string | null | undefined} [opts.baseUrl] - The base URL used to construct a WHIP URL when a stream key is provided. Defaults to "https://playback.livepeer.studio/webrtc".
* @returns {string | null} A WHIP URL derived from the provided ingest information, or null if the input is invalid, empty, or the necessary information to construct a WHIP URL is not provided.
*/
declare const getIngest: (ingest: string | LivepeerStream | CloudflareStreamData | CloudflareUrlData | null | undefined, opts?: {
baseUrl?: string | null | undefined;
}) => string | null;
/**
* Phase of the asset storage
*/
declare enum Phase {
declare enum LivepeerPhase {
Waiting = "waiting",

@@ -159,3 +188,3 @@ Processing = "processing",

}
interface Tasks {
interface LivepeerTasks {
/**

@@ -182,7 +211,7 @@ * ID of any currently running task that is exporting this

}
interface StorageStatus {
interface LivepeerStorageStatus {
/**
* Phase of the asset storage
*/
phase: Phase;
phase?: LivepeerPhase;
/**

@@ -196,3 +225,3 @@ * Current progress of the task updating the storage.

errorMessage?: string;
tasks: Tasks;
tasks?: LivepeerTasks;
}

@@ -202,9 +231,9 @@ /**

*/
declare enum PrimaryType {
declare enum LivepeerPrimaryType {
VideoAttestation = "VideoAttestation"
}
declare enum Name {
declare enum LivepeerName {
VerifiableVideo = "Verifiable Video"
}
declare enum Version {
declare enum LivepeerVersion {
One = "1"

@@ -215,9 +244,9 @@ }

*/
interface Domain {
name: Name;
version: Version;
interface LivepeerDomain {
name?: LivepeerName;
version?: LivepeerVersion;
}
interface Attestations {
role: string;
address: string;
interface LivepeerAttestations {
role?: string;
address?: string;
}

@@ -227,13 +256,13 @@ /**

*/
interface Message {
video: string;
attestations: Attestations[];
signer: string;
timestamp: number;
interface LivepeerMessage {
video?: string;
attestations?: LivepeerAttestations[];
signer?: string;
timestamp?: number;
}
declare enum SignatureType {
declare enum LivepeerSignatureType {
Eip712 = "eip712",
Flow = "flow"
}
interface AttestationIpfs {
interface LivepeerAttestationIpfs {
/**

@@ -259,7 +288,7 @@ * CID of the file on IPFS

}
interface AttestationStorage {
ipfs?: AttestationIpfs;
status?: StorageStatus;
interface LivepeerAttestationStorage {
ipfs?: LivepeerAttestationIpfs;
status?: LivepeerStorageStatus;
}
interface Attestation {
interface LivepeerAttestation {
id?: string;

@@ -269,15 +298,15 @@ /**

*/
primaryType: PrimaryType;
primaryType?: LivepeerPrimaryType;
/**
* Video Metadata EIP-712 domain
*/
domain: Domain;
domain?: LivepeerDomain;
/**
* Video Metadata EIP-712 message content
*/
message: Message;
message?: LivepeerMessage;
/**
* Video Metadata EIP-712 message signature
*/
signature: string;
signature?: string;
/**

@@ -287,6 +316,6 @@ * Timestamp (in milliseconds) at which the object was created

createdAt?: number;
signatureType?: SignatureType;
storage?: AttestationStorage;
signatureType?: LivepeerSignatureType;
storage?: LivepeerAttestationStorage;
}
declare enum TypeT {
declare enum LivepeerTypeT {
Public = "public",

@@ -299,4 +328,4 @@ Jwt = "jwt",

*/
interface PlaybackPolicy {
type: TypeT;
interface LivepeerPlaybackPolicy {
type?: LivepeerTypeT;
/**

@@ -311,3 +340,3 @@ * ID of the webhook to use for playback policy

}
declare enum PlaybackInfoType {
declare enum LivepeerPlaybackInfoType {
Live = "live",

@@ -317,6 +346,6 @@ Vod = "vod",

}
interface Source {
hrn: string;
type: string;
url: string;
interface LivepeerSource {
hrn?: string;
type?: string;
url?: string;
size?: number;

@@ -327,3 +356,3 @@ width?: number;

}
interface Meta {
interface LivepeerMeta {
live?: number;

@@ -333,11 +362,21 @@ /**

*/
playbackPolicy?: PlaybackPolicy;
source: Source[];
attestation?: Attestation;
playbackPolicy?: LivepeerPlaybackPolicy;
source?: LivepeerSource[];
attestation?: LivepeerAttestation;
}
interface PlaybackInfo {
type: PlaybackInfoType;
meta: Meta;
interface LivepeerPlaybackInfo {
type?: LivepeerPlaybackInfoType;
meta?: LivepeerMeta;
}
interface LivepeerStream {
streamKey?: string;
}
type CloudflareStreamData = {
webRTC?: CloudflareUrlData;
webRTCPlayback?: CloudflareUrlData;
};
type CloudflareUrlData = {
url?: string;
};
export { type Attestation, type AttestationIpfs, type AttestationStorage, type Attestations, type Domain, type Message, type Meta, Name, Phase, type PlaybackInfo, PlaybackInfoType, type PlaybackPolicy, PrimaryType, SignatureType, type Source, type StorageStatus, type Tasks, TypeT, Version, getSrc };
export { type CloudflareStreamData, type CloudflareUrlData, type LivepeerAttestation, type LivepeerAttestationIpfs, type LivepeerAttestationStorage, type LivepeerAttestations, type LivepeerDomain, type LivepeerMessage, type LivepeerMeta, LivepeerName, LivepeerPhase, type LivepeerPlaybackInfo, LivepeerPlaybackInfoType, type LivepeerPlaybackPolicy, LivepeerPrimaryType, LivepeerSignatureType, type LivepeerSource, type LivepeerStorageStatus, type LivepeerStream, type LivepeerTasks, LivepeerTypeT, LivepeerVersion, getIngest, getSrc };

@@ -319,6 +319,7 @@ Object.defineProperty(exports, '__esModule', { value: true });

}
const base64Mime = src.match(mimeFromBase64Pattern);
const sourceTest = src?.toLowerCase();
const base64Mime = sourceTest.match(mimeFromBase64Pattern);
const resolvedWidth = opts?.sizing?.width ?? null;
const resolvedHeight = opts?.sizing?.height ?? null;
return webrtcExtensions.test(src) ? {
return webrtcExtensions.test(sourceTest) ? {
type: "webrtc",

@@ -329,21 +330,21 @@ src: src,

height: resolvedHeight
} : hlsExtensions.test(src) ? {
} : hlsExtensions.test(sourceTest) ? {
type: "hls",
src: src,
mime: getMimeType(hlsExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(hlsExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : videoExtensions.test(src) ? {
} : videoExtensions.test(sourceTest) ? {
type: "video",
src: src,
mime: getMimeType(videoExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(videoExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : audioExtensions.test(src) ? {
} : audioExtensions.test(sourceTest) ? {
type: "audio",
src: src,
mime: getMimeType(audioExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(audioExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : base64String.test(src) ? {
} : base64String.test(sourceTest) ? {
type: "video",

@@ -354,12 +355,12 @@ src: src,

height: resolvedHeight
} : imageExtensions.test(src) ? {
} : imageExtensions.test(sourceTest) ? {
type: "image",
src: src,
mime: getMimeType(imageExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(imageExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : vttExtensions.test(src) ? {
} : vttExtensions.test(sourceTest) ? {
type: "vtt",
src: src,
mime: getMimeType(vttExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(vttExtensions.exec(sourceTest)?.[1] ?? ""),
width: null,

@@ -373,13 +374,25 @@ height: null

*
* This function is designed to handle multiple input types: `PlaybackInfo`, `Source`, `string[]`, and `string`.
* This function is designed to handle multiple input types: Livepeer playback info, Cloudflare stream data, Mux URLs, `string[]`, and `string`.
* It processes these inputs to extract or construct source objects and then transforms these into `Src` inputs.
*
* - `PlaybackInfo`: Extracts the 'source' array from its 'meta' property.
* - `Source`: Directly uses the Source object.
* These include the video playback information, as well as supporting data like thumbnails and VTT files, which can be used by the Player.
*
* The input types and their processing are as follows:
* - `LivepeerPlaybackInfo`: Extracts the 'source' array from its 'meta' property.
* - `LivepeerSource` or `LivepeerSource[]`: Uses the source object(s) directly.
* - `CloudflareStreamData`: Retrieves the stream data and constructs Source objects.
* - `CloudflareUrlData`: Uses the URL data to create a Source object.
* - `string[]`: Assumes each string as a URL and creates an array of Source objects.
* - `string`: Assumes the string is a URL and creates a single Source object.
*
* @param {PlaybackInfo | Source | string[] | string | null | undefined} playbackInfo - The playback information to be parsed.
* It can be of type `PlaybackInfo`, `Source`, `string[]` of URLs, or a single URL.
* @returns {Src[] | null} An array of `Src` derived from the provided playback information, or null if the input is invalid or empty.
* @param {LivepeerPlaybackInfo | LivepeerSource | LivepeerSource[] | CloudflareStreamData | CloudflareUrlData | string[] | string | null | undefined} source - The playback information to be parsed.
* It can be of type `LivepeerPlaybackInfo`, `LivepeerSource`, `LivepeerSource[]`, `CloudflareStreamData`, `CloudflareUrlData`, `string[]` of URLs, or a single URL.
* @returns {Src[] | null} An array of `Src` objects derived from the provided playback information, or null if the input is invalid or empty.
*
* Each `Src` object may contain the following properties:
* - `url`: The URL of the media source.
* - `size`: The size of the media file (optional).
* - `width`: The width of the media (optional).
* - `height`: The height of the media (optional).
* - `bitrate`: The bitrate of the media (optional).
*/ const getSrc = (source)=>{

@@ -397,11 +410,24 @@ if (!source) {

} else if (Array.isArray(source)) {
sources = source.map((url)=>({
url
}));
} else if ("url" in source && "type" in source && "hrn" in source) {
sources = [
source
];
} else if ("meta" in source && source.meta.source) {
sources = source.meta.source;
sources = source.map((s)=>typeof s === "string" ? {
url: s
} : s?.url ? {
...s
} : {
url: s
});
} else if (typeof source === "object") {
if ("url" in source && typeof source.url === "string") {
sources = [
source
];
} else if ("meta" in source && typeof source.meta === "object" && source.meta && "source" in source.meta && Array.isArray(source.meta.source) && // biome-ignore lint/correctness/noUnsafeOptionalChaining: allow unsafe check
"url" in source?.meta?.source?.[0]) {
sources = source.meta.source;
} else if ("webRTCPlayback" in source && typeof source.webRTCPlayback === "object" && source.webRTCPlayback && "url" in source.webRTCPlayback && typeof source.webRTCPlayback.url === "string") {
sources = [
{
url: source.webRTCPlayback.url
}
];
}
}

@@ -418,40 +444,86 @@ // Process sources to get Src[]

};
var Phase;
(function(Phase) {
Phase["Waiting"] = "waiting";
Phase["Processing"] = "processing";
Phase["Ready"] = "ready";
Phase["Failed"] = "failed";
Phase["Reverted"] = "reverted";
})(Phase || (Phase = {}));
var PrimaryType;
(function(PrimaryType) {
PrimaryType["VideoAttestation"] = "VideoAttestation";
})(PrimaryType || (PrimaryType = {}));
var Name;
(function(Name) {
Name["VerifiableVideo"] = "Verifiable Video";
})(Name || (Name = {}));
var Version;
(function(Version) {
Version["One"] = "1";
})(Version || (Version = {}));
var SignatureType;
(function(SignatureType) {
SignatureType["Eip712"] = "eip712";
SignatureType["Flow"] = "flow";
})(SignatureType || (SignatureType = {}));
var TypeT;
(function(TypeT) {
TypeT["Public"] = "public";
TypeT["Jwt"] = "jwt";
TypeT["Webhook"] = "webhook";
})(TypeT || (TypeT = {}));
var PlaybackInfoType;
(function(PlaybackInfoType) {
PlaybackInfoType["Live"] = "live";
PlaybackInfoType["Vod"] = "vod";
PlaybackInfoType["Recording"] = "recording";
})(PlaybackInfoType || (PlaybackInfoType = {}));
/**
* Parses various types of ingest information and converts them into a WHIP URL.
*
* This function is designed to handle multiple input types: strings (assumed to be stream keys or URLs), Cloudflare stream data, Cloudflare URL data, or Livepeer stream data. It processes these inputs to either return the URL directly, construct a WHIP URL using the provided base URL, or extract the URL from object data.
*
* - If the input is a valid URL (starting with http/https), it returns the URL directly.
* - If the input is a string not starting with http/https, it treats the string as a stream key and constructs a WHIP URL using the provided base URL from opts.
* - For object inputs (`CloudflareStreamData`, `CloudflareUrlData`, or `LivepeerStream`), it attempts to extract the URL from the `url`, `webRTC.url`, or `streamKey` properties.
*
* @param {string | LivepeerStream | CloudflareStreamData | CloudflareUrlData | null | undefined} ingest - The ingest information to be parsed. It can be a stream key, URL, Cloudflare stream data, Cloudflare URL data, or Livepeer stream data.
* @param {Object} [opts] - Optional parameters.
* @param {string | null | undefined} [opts.baseUrl] - The base URL used to construct a WHIP URL when a stream key is provided. Defaults to "https://playback.livepeer.studio/webrtc".
* @returns {string | null} A WHIP URL derived from the provided ingest information, or null if the input is invalid, empty, or the necessary information to construct a WHIP URL is not provided.
*/ const getIngest = (ingest, opts = {
baseUrl: "https://playback.livepeer.studio/webrtc"
})=>{
if (!ingest) {
return null;
}
if (typeof ingest === "string" && ingest) {
if (/^https?:\/\//i.test(ingest)) {
return ingest;
}
// we assume a stream key has been passed here
if (opts.baseUrl) {
return `${opts.baseUrl}/${ingest}`;
}
return null;
}
if (typeof ingest === "object") {
if ("url" in ingest && typeof ingest.url === "string" && ingest.url) {
return ingest.url;
}
if ("streamKey" in ingest && typeof ingest.streamKey === "string" && ingest.streamKey) {
if (opts.baseUrl) {
return `${opts.baseUrl}/${ingest.streamKey}`;
}
}
if ("webRTC" in ingest && typeof ingest.webRTC === "object" && ingest.webRTC && "url" in ingest.webRTC && typeof ingest.webRTC.url === "string" && typeof ingest.webRTC.url) {
return ingest.webRTC.url;
}
return null;
}
return null;
};
var LivepeerPhase;
(function(LivepeerPhase) {
LivepeerPhase["Waiting"] = "waiting";
LivepeerPhase["Processing"] = "processing";
LivepeerPhase["Ready"] = "ready";
LivepeerPhase["Failed"] = "failed";
LivepeerPhase["Reverted"] = "reverted";
})(LivepeerPhase || (LivepeerPhase = {}));
var LivepeerPrimaryType;
(function(LivepeerPrimaryType) {
LivepeerPrimaryType["VideoAttestation"] = "VideoAttestation";
})(LivepeerPrimaryType || (LivepeerPrimaryType = {}));
var LivepeerName;
(function(LivepeerName) {
LivepeerName["VerifiableVideo"] = "Verifiable Video";
})(LivepeerName || (LivepeerName = {}));
var LivepeerVersion;
(function(LivepeerVersion) {
LivepeerVersion["One"] = "1";
})(LivepeerVersion || (LivepeerVersion = {}));
var LivepeerSignatureType;
(function(LivepeerSignatureType) {
LivepeerSignatureType["Eip712"] = "eip712";
LivepeerSignatureType["Flow"] = "flow";
})(LivepeerSignatureType || (LivepeerSignatureType = {}));
var LivepeerTypeT;
(function(LivepeerTypeT) {
LivepeerTypeT["Public"] = "public";
LivepeerTypeT["Jwt"] = "jwt";
LivepeerTypeT["Webhook"] = "webhook";
})(LivepeerTypeT || (LivepeerTypeT = {}));
var LivepeerPlaybackInfoType;
(function(LivepeerPlaybackInfoType) {
LivepeerPlaybackInfoType["Live"] = "live";
LivepeerPlaybackInfoType["Vod"] = "vod";
LivepeerPlaybackInfoType["Recording"] = "recording";
})(LivepeerPlaybackInfoType || (LivepeerPlaybackInfoType = {}));
exports.getIngest = getIngest;
exports.getSrc = getSrc;

@@ -437,6 +437,7 @@ Object.defineProperty(exports, '__esModule', { value: true });

}
const base64Mime = src.match(mimeFromBase64Pattern);
const sourceTest = src?.toLowerCase();
const base64Mime = sourceTest.match(mimeFromBase64Pattern);
const resolvedWidth = opts?.sizing?.width ?? null;
const resolvedHeight = opts?.sizing?.height ?? null;
return webrtcExtensions.test(src) ? {
return webrtcExtensions.test(sourceTest) ? {
type: "webrtc",

@@ -447,21 +448,21 @@ src: src,

height: resolvedHeight
} : hlsExtensions.test(src) ? {
} : hlsExtensions.test(sourceTest) ? {
type: "hls",
src: src,
mime: getMimeType(hlsExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(hlsExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : videoExtensions.test(src) ? {
} : videoExtensions.test(sourceTest) ? {
type: "video",
src: src,
mime: getMimeType(videoExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(videoExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : audioExtensions.test(src) ? {
} : audioExtensions.test(sourceTest) ? {
type: "audio",
src: src,
mime: getMimeType(audioExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(audioExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : base64String.test(src) ? {
} : base64String.test(sourceTest) ? {
type: "video",

@@ -472,12 +473,12 @@ src: src,

height: resolvedHeight
} : imageExtensions.test(src) ? {
} : imageExtensions.test(sourceTest) ? {
type: "image",
src: src,
mime: getMimeType(imageExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(imageExtensions.exec(sourceTest)?.[1] ?? ""),
width: resolvedWidth,
height: resolvedHeight
} : vttExtensions.test(src) ? {
} : vttExtensions.test(sourceTest) ? {
type: "vtt",
src: src,
mime: getMimeType(vttExtensions.exec(src)?.[1] ?? ""),
mime: getMimeType(vttExtensions.exec(sourceTest)?.[1] ?? ""),
width: null,

@@ -484,0 +485,0 @@ height: null

declare const version: {
readonly core: "@livepeer/core@3.0.0";
readonly react: "@livepeer/react@4.0.0";
readonly core: "@livepeer/core@3.0.1";
readonly react: "@livepeer/react@4.0.1";
};
export { version };
Object.defineProperty(exports, '__esModule', { value: true });
const core = "@livepeer/core@3.0.0";
const react = "@livepeer/react@4.0.0";
const core = "@livepeer/core@3.0.1";
const react = "@livepeer/react@4.0.1";
const version = {

@@ -6,0 +6,0 @@ core,

@@ -5,3 +5,3 @@ {

"license": "MIT",
"version": "3.0.0",
"version": "3.0.1",
"repository": {

@@ -8,0 +8,0 @@ "type": "git",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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