Socket
Socket
Sign inDemoInstall

html2canvas

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

html2canvas - npm Package Compare versions

Comparing version 1.0.0-rc.4 to 1.0.0-rc.5

dist/lib/__tests__/index.js

11

CHANGELOG.md

@@ -5,2 +5,13 @@ # Change Log

# [1.0.0-rc.5](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.4...v1.0.0-rc.5) (2019-09-27)
### fix
* correctly respect logging option (#2013) ([34b06d6365603c3b16664ab7804efe94c7945946](https://github.com/niklasvh/html2canvas/commit/34b06d6365603c3b16664ab7804efe94c7945946)), closes [#2013](https://github.com/niklasvh/html2canvas/issues/2013)
* safari pseudo element content parsing (#2018) ([3f599103fb139f218ffe917800e74af2c7cc7ad5](https://github.com/niklasvh/html2canvas/commit/3f599103fb139f218ffe917800e74af2c7cc7ad5)), closes [#2018](https://github.com/niklasvh/html2canvas/issues/2018)
* using existing canvas option (#2017) ([076492042a73d67b30e4562f2964200e07d25f5e](https://github.com/niklasvh/html2canvas/commit/076492042a73d67b30e4562f2964200e07d25f5e)), closes [#2017](https://github.com/niklasvh/html2canvas/issues/2017)
# [1.0.0-rc.4](https://github.com/niklasvh/html2canvas/compare/v1.0.0-rc.3...v1.0.0-rc.4) (2019-09-22)

@@ -7,0 +18,0 @@

78

dist/lib/core/__tests__/cache-storage.js
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

@@ -41,3 +52,38 @@ return new (P || (P = Promise))(function (resolve, reject) {

var features_1 = require("../features");
var mock_context_1 = require("./mock-context");
var cache_storage_1 = require("../cache-storage");
var logger_1 = require("../logger");
var proxy = 'http://example.com/proxy';
var createMockContext = function (origin, opts) {
if (opts === void 0) { opts = {}; }
var context = {
location: {
href: origin
},
document: {
createElement: function (_name) {
var _href = '';
return {
set href(value) {
_href = value;
},
get href() {
return _href;
},
get protocol() {
return new URL(_href).protocol;
},
get hostname() {
return new URL(_href).hostname;
},
get port() {
return new URL(_href).port;
}
};
}
}
};
cache_storage_1.CacheStorage.setContext(context);
logger_1.Logger.create({ id: 'test', enabled: false });
return cache_storage_1.CacheStorage.create('test', __assign({ imageTimeout: 0, useCORS: false, allowTaint: false, proxy: proxy }, opts));
};
var images = [];

@@ -122,3 +168,3 @@ var xhr = [];

case 0:
cache = mock_context_1.createMockContext('http://example.com', { proxy: null });
cache = createMockContext('http://example.com', { proxy: null });
return [4 /*yield*/, cache.addImage('http://example.com/test.jpg')];

@@ -142,3 +188,3 @@ case 1:

case 0:
cache = mock_context_1.createMockContext('http://example.com');
cache = createMockContext('http://example.com');
return [4 /*yield*/, cache.addImage('http://example.com/test.jpg')];

@@ -162,3 +208,3 @@ case 1:

case 0:
cache = mock_context_1.createMockContext('http://example.com');
cache = createMockContext('http://example.com');
return [4 /*yield*/, cache.addImage('http://example.com/test.svg')];

@@ -183,3 +229,3 @@ case 1:

setFeatures({ SUPPORT_SVG_DRAWING: false });
cache = mock_context_1.createMockContext('http://example.com');
cache = createMockContext('http://example.com');
return [4 /*yield*/, cache.addImage('http://example.com/test.svg')];

@@ -203,3 +249,3 @@ case 1:

case 0:
cache = mock_context_1.createMockContext('http://example.com', {
cache = createMockContext('http://example.com', {
proxy: undefined

@@ -220,3 +266,3 @@ });

case 0:
cache = mock_context_1.createMockContext('http://example.com', {
cache = createMockContext('http://example.com', {
allowTaint: true,

@@ -240,3 +286,3 @@ proxy: undefined

case 0:
cache = mock_context_1.createMockContext('http://example.com', { useCORS: true });
cache = createMockContext('http://example.com', { useCORS: true });
return [4 /*yield*/, cache.addImage('http://html2canvas.hertzen.com/test.jpg')];

@@ -258,3 +304,3 @@ case 1:

setFeatures({ SUPPORT_CORS_IMAGES: false });
cache = mock_context_1.createMockContext('http://example.com', {
cache = createMockContext('http://example.com', {
useCORS: true,

@@ -276,3 +322,3 @@ proxy: undefined

case 0:
cache = mock_context_1.createMockContext('http://example.com', { useCORS: true });
cache = createMockContext('http://example.com', { useCORS: true });
return [4 /*yield*/, cache.addImage('http://html2canvas.hertzen.com/test.jpg')];

@@ -293,3 +339,3 @@ case 1:

case 0:
cache = mock_context_1.createMockContext('http://example.com');
cache = createMockContext('http://example.com');
return [4 /*yield*/, cache.addImage('http://html2canvas.hertzen.com/test.jpg')];

@@ -299,3 +345,3 @@ case 1:

assert_1.deepStrictEqual(xhr.length, 1);
assert_1.deepStrictEqual(xhr[0].url, mock_context_1.proxy + "?url=" + encodeURIComponent('http://html2canvas.hertzen.com/test.jpg') + "&responseType=text");
assert_1.deepStrictEqual(xhr[0].url, proxy + "?url=" + encodeURIComponent('http://html2canvas.hertzen.com/test.jpg') + "&responseType=text");
return [4 /*yield*/, xhr[0].load(200, '<data response>')];

@@ -315,3 +361,3 @@ case 2:

case 0:
cache = mock_context_1.createMockContext('http://example.com', {
cache = createMockContext('http://example.com', {
imageTimeout: 10

@@ -323,3 +369,3 @@ });

assert_1.deepStrictEqual(xhr.length, 1);
assert_1.deepStrictEqual(xhr[0].url, mock_context_1.proxy + "?url=" + encodeURIComponent('http://html2canvas.hertzen.com/test.jpg') + "&responseType=text");
assert_1.deepStrictEqual(xhr[0].url, proxy + "?url=" + encodeURIComponent('http://html2canvas.hertzen.com/test.jpg') + "&responseType=text");
assert_1.deepStrictEqual(xhr[0].timeout, 10);

@@ -350,3 +396,3 @@ if (xhr[0].ontimeout) {

case 0:
cache = mock_context_1.createMockContext('http://example.com');
cache = createMockContext('http://example.com');
return [4 /*yield*/, cache.addImage('http://example.com/test.jpg')];

@@ -371,3 +417,3 @@ case 1:

case 0:
cache = mock_context_1.createMockContext('http://example.com', { imageTimeout: 10 });
cache = createMockContext('http://example.com', { imageTimeout: 10 });
cache.addImage('http://example.com/test.jpg');

@@ -374,0 +420,0 @@ _a.label = 1;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Logger = /** @class */ (function () {
function Logger(id) {
function Logger(_a) {
var id = _a.id, enabled = _a.enabled;
this.id = id;
this.enabled = enabled;
this.start = Date.now();

@@ -14,10 +16,12 @@ }

}
// eslint-disable-next-line no-console
if (typeof window !== 'undefined' && window.console && typeof console.debug === 'function') {
if (this.enabled) {
// eslint-disable-next-line no-console
console.debug.apply(console, [this.id, this.getTime() + "ms"].concat(args));
if (typeof window !== 'undefined' && window.console && typeof console.debug === 'function') {
// eslint-disable-next-line no-console
console.debug.apply(console, [this.id, this.getTime() + "ms"].concat(args));
}
else {
this.info.apply(this, args);
}
}
else {
this.info.apply(this, args);
}
};

@@ -27,4 +31,4 @@ Logger.prototype.getTime = function () {

};
Logger.create = function (id) {
Logger.instances[id] = new Logger(id);
Logger.create = function (options) {
Logger.instances[options.id] = new Logger(options);
};

@@ -47,6 +51,8 @@ Logger.destroy = function (id) {

}
// eslint-disable-next-line no-console
if (typeof window !== 'undefined' && window.console && typeof console.info === 'function') {
if (this.enabled) {
// eslint-disable-next-line no-console
console.info.apply(console, [this.id, this.getTime() + "ms"].concat(args));
if (typeof window !== 'undefined' && window.console && typeof console.info === 'function') {
// eslint-disable-next-line no-console
console.info.apply(console, [this.id, this.getTime() + "ms"].concat(args));
}
}

@@ -60,10 +66,12 @@ };

}
// eslint-disable-next-line no-console
if (typeof window !== 'undefined' && window.console && typeof console.error === 'function') {
if (this.enabled) {
// eslint-disable-next-line no-console
console.error.apply(console, [this.id, this.getTime() + "ms"].concat(args));
if (typeof window !== 'undefined' && window.console && typeof console.error === 'function') {
// eslint-disable-next-line no-console
console.error.apply(console, [this.id, this.getTime() + "ms"].concat(args));
}
else {
this.info.apply(this, args);
}
}
else {
this.info.apply(this, args);
}
};

@@ -70,0 +78,0 @@ Logger.instances = {};

@@ -9,9 +9,6 @@ "use strict";

var angle_1 = require("../../types/angle");
var mock_context_1 = require("../../../core/__tests__/mock-context");
var cache_storage_1 = require("../../../core/cache-storage");
jest.mock('../../../core/cache-storage');
jest.mock('../../../core/features');
var backgroundImageParse = function (value) { return background_image_1.backgroundImage.parse(parser_1.Parser.parseValues(value)); };
describe('property-descriptors', function () {
before(function () {
cache_storage_1.CacheStorage.attachInstance(mock_context_1.createMockContext('http://example.com'));
});
describe('background-image', function () {

@@ -18,0 +15,0 @@ it('none', function () { return assert_1.deepStrictEqual(backgroundImageParse('none'), []); });

@@ -11,2 +11,4 @@ "use strict";

var colorParse = function (value) { return color_1.color.parse(parser_1.Parser.parseValue(value)); };
jest.mock('../../../core/cache-storage');
jest.mock('../../../core/features');
describe('types', function () {

@@ -13,0 +15,0 @@ describe('<image>', function () {

@@ -29,6 +29,8 @@ "use strict";

var counterReset = style.counterReset;
var canReset = true;
if (counterIncrement !== null) {
counterIncrement.forEach(function (entry) {
var counter = _this.counters[entry.counter];
if (counter) {
if (counter && entry.increment !== 0) {
canReset = false;
counter[Math.max(0, counter.length - 1)] += entry.increment;

@@ -39,10 +41,12 @@ }

var counterNames = [];
counterReset.forEach(function (entry) {
var counter = _this.counters[entry.counter];
counterNames.push(entry.counter);
if (!counter) {
counter = _this.counters[entry.counter] = [];
}
counter.push(entry.reset);
});
if (canReset) {
counterReset.forEach(function (entry) {
var counter = _this.counters[entry.counter];
counterNames.push(entry.counter);
if (!counter) {
counter = _this.counters[entry.counter] = [];
}
counter.push(entry.reset);
});
}
return counterNames;

@@ -49,0 +53,0 @@ };

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });

@@ -37,24 +72,36 @@ var node_parser_1 = require("./node-parser");

*/
var iframeLoad = iframeLoader(iframe).then(function () {
_this.scrolledElements.forEach(restoreNodeScroll);
if (cloneWindow) {
cloneWindow.scrollTo(windowSize.left, windowSize.top);
if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent) &&
(cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
documentClone.documentElement.style.top = -windowSize.top + 'px';
documentClone.documentElement.style.left = -windowSize.left + 'px';
documentClone.documentElement.style.position = 'absolute';
var iframeLoad = iframeLoader(iframe).then(function () { return __awaiter(_this, void 0, void 0, function () {
var onclone;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
this.scrolledElements.forEach(restoreNodeScroll);
if (cloneWindow) {
cloneWindow.scrollTo(windowSize.left, windowSize.top);
if (/(iPad|iPhone|iPod)/g.test(navigator.userAgent) &&
(cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
documentClone.documentElement.style.top = -windowSize.top + 'px';
documentClone.documentElement.style.left = -windowSize.left + 'px';
documentClone.documentElement.style.position = 'absolute';
}
}
onclone = this.options.onclone;
if (typeof this.clonedReferenceElement === 'undefined') {
return [2 /*return*/, Promise.reject("Error finding the " + this.referenceElement.nodeName + " in the cloned document")];
}
if (!(documentClone.fonts && documentClone.fonts.ready)) return [3 /*break*/, 2];
return [4 /*yield*/, documentClone.fonts.ready];
case 1:
_a.sent();
_a.label = 2;
case 2:
if (typeof onclone === 'function') {
return [2 /*return*/, Promise.resolve()
.then(function () { return onclone(documentClone); })
.then(function () { return iframe; })];
}
return [2 /*return*/, iframe];
}
}
var onclone = _this.options.onclone;
if (typeof _this.clonedReferenceElement === 'undefined') {
return Promise.reject("Error finding the " + _this.referenceElement.nodeName + " in the cloned document");
}
if (typeof onclone === 'function') {
return Promise.resolve()
.then(function () { return onclone(documentClone); })
.then(function () { return iframe; });
}
return iframe;
});
});
}); });
documentClone.open();

@@ -217,3 +264,3 @@ documentClone.write(serializeDoctype(document.doctype) + "<html></html>");

var counters = this.counters.parse(new index_1.CSSParsedCounterDeclaration(style));
var before_1 = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE);
var before = this.resolvePseudoContent(node, clone, styleBefore, PseudoElementType.BEFORE);
for (var child = node.firstChild; child; child = child.nextSibling) {

@@ -229,8 +276,8 @@ if (!node_parser_1.isElementNode(child) ||

}
if (before_1) {
clone.insertBefore(before_1, clone.firstChild);
if (before) {
clone.insertBefore(before, clone.firstChild);
}
var after_1 = this.resolvePseudoContent(node, clone, styleAfter, PseudoElementType.AFTER);
if (after_1) {
clone.appendChild(after_1);
var after = this.resolvePseudoContent(node, clone, styleAfter, PseudoElementType.AFTER);
if (after) {
clone.appendChild(after);
}

@@ -321,3 +368,4 @@ this.counters.pop(counters);

default:
// console.log('ident', token, declaration);
// safari doesn't parse string tokens correctly because of lack of quotes
anonymousReplacedElement.appendChild(document.createTextNode(token.value));
}

@@ -333,2 +381,9 @@ }

};
DocumentCloner.destroy = function (container) {
if (container.parentNode) {
container.parentNode.removeChild(container);
return true;
}
return false;
};
return DocumentCloner;

@@ -335,0 +390,0 @@ }());

@@ -107,3 +107,3 @@ "use strict";

windowBounds = new bounds_1.Bounds(options.scrollX, options.scrollY, options.windowWidth, options.windowHeight);
logger_1.Logger.create(instanceName);
logger_1.Logger.create({ id: instanceName, enabled: options.logging });
logger_1.Logger.getInstance(instanceName).debug("Starting document clone");

@@ -142,2 +142,3 @@ documentCloner = new document_cloner_1.DocumentCloner(element, {

cache: options.cache,
canvas: options.canvas,
backgroundColor: backgroundColor,

@@ -178,3 +179,3 @@ scale: options.scale,

if (options.removeContainer === true) {
if (!cleanContainer(container)) {
if (!document_cloner_1.DocumentCloner.destroy(container)) {
logger_1.Logger.getInstance(instanceName).error("Cannot detach cloned iframe as it is not in the DOM anymore");

@@ -190,9 +191,2 @@ }

}); };
var cleanContainer = function (container) {
if (container.parentNode) {
container.parentNode.removeChild(container);
return true;
}
return false;
};
//# sourceMappingURL=index.js.map

@@ -77,6 +77,8 @@ "use strict";

this.options = options;
this.canvas.width = Math.floor(options.width * options.scale);
this.canvas.height = Math.floor(options.height * options.scale);
this.canvas.style.width = options.width + "px";
this.canvas.style.height = options.height + "px";
if (!options.canvas) {
this.canvas.width = Math.floor(options.width * options.scale);
this.canvas.height = Math.floor(options.height * options.scale);
this.canvas.style.width = options.width + "px";
this.canvas.style.height = options.height + "px";
}
this.fontMetrics = new font_metrics_1.FontMetrics(document);

@@ -625,3 +627,3 @@ this.ctx.scale(this.options.scale, this.options.scale);

ctx.fillRect(0, 0, width, height);
if ((width > 0) && (height > 0)) {
if (width > 0 && height > 0) {
pattern = this_1.ctx.createPattern(canvas, 'repeat');

@@ -628,0 +630,0 @@ this_1.renderRepeat(path, pattern, x, y);

@@ -0,1 +1,5 @@

export interface LoggerOptions {
id: string;
enabled: boolean;
}
export declare class Logger {

@@ -6,7 +10,8 @@ static instances: {

private readonly id;
private readonly enabled;
private readonly start;
constructor(id: string);
constructor({ id, enabled }: LoggerOptions);
debug(...args: any): void;
getTime(): number;
static create(id: string): void;
static create(options: LoggerOptions): void;
static destroy(id: string): void;

@@ -13,0 +18,0 @@ static getInstance(id: string): Logger;

@@ -26,2 +26,3 @@ import { Bounds } from '../css/layout/bounds';

resolvePseudoContent(node: Element, clone: Element, style: CSSStyleDeclaration, pseudoElt: PseudoElementType): HTMLElement | void;
static destroy(container: HTMLIFrameElement): boolean;
}

@@ -28,0 +29,0 @@ declare enum PseudoElementType {

@@ -5,3 +5,3 @@ import { CloneOptions } from './dom/document-cloner';

export declare type Options = CloneOptions & RenderOptions & ResourceOptions & {
backgroundColor: string;
backgroundColor: string | null;
foreignObjectRendering: boolean;

@@ -8,0 +8,0 @@ logging: boolean;

@@ -0,1 +1,5 @@

export interface LoggerOptions {
id: string;
enabled: boolean;
}
export declare class Logger {

@@ -6,7 +10,8 @@ static instances: {

private readonly id;
private readonly enabled;
private readonly start;
constructor(id: string);
constructor({ id, enabled }: LoggerOptions);
debug(...args: any): void;
getTime(): number;
static create(id: string): void;
static create(options: LoggerOptions): void;
static destroy(id: string): void;

@@ -13,0 +18,0 @@ static getInstance(id: string): Logger;

@@ -26,2 +26,3 @@ import { Bounds } from '../css/layout/bounds';

resolvePseudoContent(node: Element, clone: Element, style: CSSStyleDeclaration, pseudoElt: PseudoElementType): HTMLElement | void;
static destroy(container: HTMLIFrameElement): boolean;
}

@@ -28,0 +29,0 @@ declare enum PseudoElementType {

@@ -5,3 +5,3 @@ import { CloneOptions } from './dom/document-cloner';

export declare type Options = CloneOptions & RenderOptions & ResourceOptions & {
backgroundColor: string;
backgroundColor: string | null;
foreignObjectRendering: boolean;

@@ -8,0 +8,0 @@ logging: boolean;

@@ -9,3 +9,3 @@ {

"browser": "dist/html2canvas.js",
"version": "1.0.0-rc.4",
"version": "1.0.0-rc.5",
"author": {

@@ -33,2 +33,3 @@ "name": "Niklas von Hertzen",

"@types/glob": "^7.1.1",
"@types/jest": "^24.0.18",
"@types/mocha": "^5.2.6",

@@ -58,2 +59,3 @@ "@types/node": "^11.13.2",

"html2canvas-proxy": "1.0.1",
"jest": "^24.9.0",
"jquery": "^3.4.0",

@@ -85,2 +87,3 @@ "js-polyfills": "^0.1.42",

"standard-version": "^5.0.2",
"ts-jest": "^24.1.0",
"ts-loader": "^5.3.3",

@@ -106,3 +109,3 @@ "ts-node": "^8.0.3",

"test": "npm run lint && npm run unittest && npm run karma",
"unittest": "mocha --require ts-node/register src/**/__tests__/*.ts",
"unittest": "jest",
"karma": "node karma",

@@ -109,0 +112,0 @@ "watch": "rollup -c rollup.config.ts -w",

@@ -10,3 +10,3 @@ {

"resolveJsonModule": true,
"typeRoots": [ "./types", "./node_modules/@types"],
"types": ["node", "jest"],
"target": "es5",

@@ -13,0 +13,0 @@ "lib": ["es2015", "dom"],

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

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

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