Replace tldjs with @browserpass/url, check port (#179)
This commit is contained in:
@@ -103,7 +103,7 @@ If not, repeat the installation instructions for the extension.
|
|||||||
|
|
||||||
Browserpass was designed with an assumption that certain conventions are being followed when organizing your password store.
|
Browserpass was designed with an assumption that certain conventions are being followed when organizing your password store.
|
||||||
|
|
||||||
1. In order to benefit of phishing attack protection, a password entry file, or any of its parent folders, must contain a full domain name (including TLD like `.com`) in their name in order to automatically match a website. However, entries which do not contain such a domain in their name may still be manually selected.
|
1. In order to benefit of phishing attack protection, a password entry file, or any of its parent folders, must contain a full domain name (including TLD like `.com`) and optionally port in their name in order to automatically match a website. However, entries which do not contain such a domain in their name may still be manually selected.
|
||||||
|
|
||||||
File names are not allowed to contain `\` or `/` characters, because both of them are considered to be path separators.
|
File names are not allowed to contain `\` or `/` characters, because both of them are considered to be path separators.
|
||||||
|
|
||||||
@@ -171,6 +171,8 @@ In order for Browserpass to correctly determine matching entries, it is expected
|
|||||||
|
|
||||||
Browserpass will display entries for the current domain, as well as all parent entries, but not entries from different subdomains. Suppose you are currently on `https://v3.app.example.com`, Browserpass will present all the following entries in popup (if they exist): `v3.app.example.com`, `app.example.com`, `example.com`; but it will not present entries like `v2.app.example.com` or `wiki.example.com`.
|
Browserpass will display entries for the current domain, as well as all parent entries, but not entries from different subdomains. Suppose you are currently on `https://v3.app.example.com`, Browserpass will present all the following entries in popup (if they exist): `v3.app.example.com`, `app.example.com`, `example.com`; but it will not present entries like `v2.app.example.com` or `wiki.example.com`.
|
||||||
|
|
||||||
|
Browserpass can also distinguish credentials meant for different ports, so for example an entry `example.com.gpg` will show up in Browserpass popup when you browse `example.com` on any port, however an entry `example.com:8080.gpg` will only show up on `8080` port.
|
||||||
|
|
||||||
Finally Browserpass will also present entries that you have recently used on this domain, even if they don't actually meet the usual matching requirements. Suppose you have a password for `amazon.com`, but you open `https://amazon.co.uk`, at first Browserpass will present no entries (because nothing matches `amazon.co.uk`), but if you hit <kbd>Backspace</kbd>, find `amazon.com` and use it to login, next time you visit `https://amazon.co.uk` and open Browserpass, `amazon.com` entry will already be present.
|
Finally Browserpass will also present entries that you have recently used on this domain, even if they don't actually meet the usual matching requirements. Suppose you have a password for `amazon.com`, but you open `https://amazon.co.uk`, at first Browserpass will present no entries (because nothing matches `amazon.co.uk`), but if you hit <kbd>Backspace</kbd>, find `amazon.com` and use it to login, next time you visit `https://amazon.co.uk` and open Browserpass, `amazon.com` entry will already be present.
|
||||||
|
|
||||||
The sorting algorithm implemented in Browserpass will use several intuitions to try to order results in the most expected way for a user:
|
The sorting algorithm implemented in Browserpass will use several intuitions to try to order results in the most expected way for a user:
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
require("chrome-extension-async");
|
require("chrome-extension-async");
|
||||||
const sha1 = require("sha1");
|
const sha1 = require("sha1");
|
||||||
const idb = require("idb");
|
const idb = require("idb");
|
||||||
|
const BrowserpassURL = require("@browserpass/url");
|
||||||
const helpers = require("./helpers");
|
const helpers = require("./helpers");
|
||||||
|
|
||||||
// native application id
|
// native application id
|
||||||
@@ -137,7 +138,7 @@ async function updateMatchingPasswordsCount(tabId, forceRefresh = false) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const tab = await chrome.tabs.get(tabId);
|
const tab = await chrome.tabs.get(tabId);
|
||||||
badgeCache.settings.host = new URL(tab.url).hostname;
|
badgeCache.settings.origin = new URL(tab.url).origin;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Unable to determine domain of the tab with id ${tabId}`);
|
throw new Error(`Unable to determine domain of the tab with id ${tabId}`);
|
||||||
}
|
}
|
||||||
@@ -146,7 +147,7 @@ async function updateMatchingPasswordsCount(tabId, forceRefresh = false) {
|
|||||||
const files = helpers.ignoreFiles(badgeCache.files, badgeCache.settings);
|
const files = helpers.ignoreFiles(badgeCache.files, badgeCache.settings);
|
||||||
const logins = helpers.prepareLogins(files, badgeCache.settings);
|
const logins = helpers.prepareLogins(files, badgeCache.settings);
|
||||||
const matchedPasswordsCount = logins.reduce(
|
const matchedPasswordsCount = logins.reduce(
|
||||||
(acc, login) => acc + (login.recent.count || login.inCurrentDomain ? 1 : 0),
|
(acc, login) => acc + (login.recent.count || login.inCurrentHost ? 1 : 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -228,13 +229,14 @@ async function saveRecent(settings, login, remove = false) {
|
|||||||
login.recent.count++;
|
login.recent.count++;
|
||||||
}
|
}
|
||||||
login.recent.when = Date.now();
|
login.recent.when = Date.now();
|
||||||
settings.recent[sha1(settings.host + sha1(login.store.id + sha1(login.login)))] = login.recent;
|
settings.recent[sha1(settings.origin + sha1(login.store.id + sha1(login.login)))] =
|
||||||
|
login.recent;
|
||||||
|
|
||||||
// save to local storage
|
// save to local storage
|
||||||
localStorage.setItem("recent", JSON.stringify(settings.recent));
|
localStorage.setItem("recent", JSON.stringify(settings.recent));
|
||||||
|
|
||||||
// a new entry was added to the popup matching list, need to refresh the count
|
// a new entry was added to the popup matching list, need to refresh the count
|
||||||
if (!login.inCurrentDomain && login.recent.count === 1) {
|
if (!login.inCurrentHost && login.recent.count === 1) {
|
||||||
updateMatchingPasswordsCount(settings.tab.id, true);
|
updateMatchingPasswordsCount(settings.tab.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +248,7 @@ async function saveRecent(settings, login, remove = false) {
|
|||||||
db.createObjectStore("log", { keyPath: "time" });
|
db.createObjectStore("log", { keyPath: "time" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await db.add("log", { time: Date.now(), host: settings.host, login: login.login });
|
await db.add("log", { time: Date.now(), host: settings.origin, login: login.login });
|
||||||
} catch {
|
} catch {
|
||||||
// ignore any errors and proceed without saving a log entry to Indexed DB
|
// ignore any errors and proceed without saving a log entry to Indexed DB
|
||||||
}
|
}
|
||||||
@@ -266,7 +268,7 @@ async function dispatchFill(settings, request, allFrames, allowForeign, allowNoS
|
|||||||
request = Object.assign(deepCopy(request), {
|
request = Object.assign(deepCopy(request), {
|
||||||
allowForeign: allowForeign,
|
allowForeign: allowForeign,
|
||||||
allowNoSecret: allowNoSecret,
|
allowNoSecret: allowNoSecret,
|
||||||
foreignFills: settings.foreignFills[settings.host] || {}
|
foreignFills: settings.foreignFills[settings.origin] || {}
|
||||||
});
|
});
|
||||||
|
|
||||||
let perFrameResults = await chrome.tabs.executeScript(settings.tab.id, {
|
let perFrameResults = await chrome.tabs.executeScript(settings.tab.id, {
|
||||||
@@ -284,10 +286,10 @@ async function dispatchFill(settings, request, allFrames, allowForeign, allowNoS
|
|||||||
let foreignFillsChanged = false;
|
let foreignFillsChanged = false;
|
||||||
for (let frame of perFrameResults) {
|
for (let frame of perFrameResults) {
|
||||||
if (typeof frame.foreignFill !== "undefined") {
|
if (typeof frame.foreignFill !== "undefined") {
|
||||||
if (typeof settings.foreignFills[settings.host] === "undefined") {
|
if (typeof settings.foreignFills[settings.origin] === "undefined") {
|
||||||
settings.foreignFills[settings.host] = {};
|
settings.foreignFills[settings.origin] = {};
|
||||||
}
|
}
|
||||||
settings.foreignFills[settings.host][frame.foreignOrigin] = frame.foreignFill;
|
settings.foreignFills[settings.origin][frame.foreignOrigin] = frame.foreignFill;
|
||||||
foreignFillsChanged = true;
|
foreignFillsChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +312,7 @@ async function dispatchFill(settings, request, allFrames, allowForeign, allowNoS
|
|||||||
async function dispatchFocusOrSubmit(settings, request, allFrames, allowForeign) {
|
async function dispatchFocusOrSubmit(settings, request, allFrames, allowForeign) {
|
||||||
request = Object.assign(deepCopy(request), {
|
request = Object.assign(deepCopy(request), {
|
||||||
allowForeign: allowForeign,
|
allowForeign: allowForeign,
|
||||||
foreignFills: settings.foreignFills[settings.host] || {}
|
foreignFills: settings.foreignFills[settings.origin] || {}
|
||||||
});
|
});
|
||||||
|
|
||||||
let perFrameResults = await chrome.tabs.executeScript(settings.tab.id, {
|
let perFrameResults = await chrome.tabs.executeScript(settings.tab.id, {
|
||||||
@@ -417,7 +419,7 @@ async function fillFields(settings, login, fields) {
|
|||||||
// try again using all available frames if we couldn't fill an "important" field
|
// try again using all available frames if we couldn't fill an "important" field
|
||||||
if (
|
if (
|
||||||
!filledFields.includes(importantFieldToFill) &&
|
!filledFields.includes(importantFieldToFill) &&
|
||||||
settings.foreignFills[settings.host] !== false
|
settings.foreignFills[settings.origin] !== false
|
||||||
) {
|
) {
|
||||||
allowForeign = true;
|
allowForeign = true;
|
||||||
filledFields = filledFields.concat(
|
filledFields = filledFields.concat(
|
||||||
@@ -455,7 +457,7 @@ async function fillFields(settings, login, fields) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try again using all available frames
|
// try again using all available frames
|
||||||
if (!filledFields.length && settings.foreignFills[settings.host] !== false) {
|
if (!filledFields.length && settings.foreignFills[settings.origin] !== false) {
|
||||||
allowForeign = true;
|
allowForeign = true;
|
||||||
filledFields = filledFields.concat(
|
filledFields = filledFields.concat(
|
||||||
await dispatchFill(
|
await dispatchFill(
|
||||||
@@ -581,7 +583,9 @@ async function getFullSettings() {
|
|||||||
// Fill current tab info
|
// Fill current tab info
|
||||||
try {
|
try {
|
||||||
settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
|
settings.tab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
|
||||||
settings.host = new URL(settings.tab.url).hostname;
|
let originInfo = new BrowserpassURL(settings.tab.url);
|
||||||
|
settings.host = originInfo.host; // TODO remove this after OTP extension is migrated
|
||||||
|
settings.origin = originInfo.origin;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
@@ -772,7 +776,7 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
case "launch":
|
case "launch":
|
||||||
case "launchInNewTab":
|
case "launchInNewTab":
|
||||||
try {
|
try {
|
||||||
var url = message.login.fields.url || message.login.domain;
|
var url = message.login.fields.url || message.login.host;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error("No URL is defined for this entry");
|
throw new Error("No URL is defined for this entry");
|
||||||
}
|
}
|
||||||
@@ -1084,6 +1088,7 @@ function triggerOTPExtension(settings, action, otp) {
|
|||||||
otp: otp,
|
otp: otp,
|
||||||
settings: {
|
settings: {
|
||||||
host: settings.host,
|
host: settings.host,
|
||||||
|
origin: settings.origin,
|
||||||
tab: settings.tab
|
tab: settings.tab
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -2,12 +2,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const FuzzySort = require("fuzzysort");
|
const FuzzySort = require("fuzzysort");
|
||||||
const TldJS = require("tldjs");
|
|
||||||
const sha1 = require("sha1");
|
const sha1 = require("sha1");
|
||||||
const ignore = require("ignore");
|
const ignore = require("ignore");
|
||||||
|
const BrowserpassURL = require("@browserpass/url");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
pathToDomain,
|
|
||||||
prepareLogins,
|
prepareLogins,
|
||||||
filterSortLogins,
|
filterSortLogins,
|
||||||
ignoreFiles
|
ignoreFiles
|
||||||
@@ -18,30 +17,30 @@ module.exports = {
|
|||||||
/**
|
/**
|
||||||
* Get the deepest available domain component of a path
|
* Get the deepest available domain component of a path
|
||||||
*
|
*
|
||||||
* @since 3.0.0
|
* @since 3.2.3
|
||||||
*
|
*
|
||||||
* @param string path Path to parse
|
* @param string path Path to parse
|
||||||
* @param string currentHost Current hostname for the active tab
|
* @param object currentHost Current host info for the active tab
|
||||||
* @return string|null Extracted domain
|
* @return object|null Extracted domain info
|
||||||
*/
|
*/
|
||||||
function pathToDomain(path, currentHost) {
|
function pathToInfo(path, currentHost) {
|
||||||
var parts = path.split(/\//).reverse();
|
var parts = path.split(/\//).reverse();
|
||||||
for (var key in parts) {
|
for (var key in parts) {
|
||||||
if (parts[key].indexOf("@") >= 0) {
|
if (parts[key].indexOf("@") >= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var t = TldJS.parse(parts[key]);
|
var info = BrowserpassURL.parseHost(parts[key]);
|
||||||
|
|
||||||
// Part is considered to be a domain component in one of the following cases:
|
// Part is considered to be a domain component in one of the following cases:
|
||||||
// - it is a valid domain with well-known TLD (github.com, login.github.com)
|
// - it is a valid domain with well-known TLD (github.com, login.github.com)
|
||||||
// - it is a valid domain with unknown TLD but the current host is it's subdomain (login.pi.hole)
|
// - it is or isn't a valid domain with any TLD but the current host matches it EXACTLY (localhost, pi.hole)
|
||||||
// - it is or isnt a valid domain but the current host matches it EXACTLY (localhost, pi.hole)
|
// - it is or isn't a valid domain with any TLD but the current host is its subdomain (login.pi.hole)
|
||||||
if (
|
if (
|
||||||
t.isValid &&
|
info.validDomain ||
|
||||||
((t.domain !== null && (t.tldExists || currentHost.endsWith(`.${t.hostname}`))) ||
|
currentHost.hostname === info.hostname ||
|
||||||
currentHost === t.hostname)
|
currentHost.hostname.endsWith(`.${info.hostname}`)
|
||||||
) {
|
) {
|
||||||
return t.hostname;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +59,7 @@ function pathToDomain(path, currentHost) {
|
|||||||
function prepareLogins(files, settings) {
|
function prepareLogins(files, settings) {
|
||||||
const logins = [];
|
const logins = [];
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
let origin = new BrowserpassURL(settings.origin);
|
||||||
|
|
||||||
for (let storeId in files) {
|
for (let storeId in files) {
|
||||||
for (let key in files[storeId]) {
|
for (let key in files[storeId]) {
|
||||||
@@ -70,11 +70,40 @@ function prepareLogins(files, settings) {
|
|||||||
login: files[storeId][key].replace(/\.gpg$/i, ""),
|
login: files[storeId][key].replace(/\.gpg$/i, ""),
|
||||||
allowFill: true
|
allowFill: true
|
||||||
};
|
};
|
||||||
login.domain = pathToDomain(storeId + "/" + login.login, settings.host);
|
|
||||||
login.inCurrentDomain =
|
// extract url info from path
|
||||||
settings.host == login.domain || settings.host.endsWith("." + login.domain);
|
let pathInfo = pathToInfo(storeId + "/" + login.login, origin);
|
||||||
|
if (pathInfo) {
|
||||||
|
// set assumed host
|
||||||
|
login.host = pathInfo.port
|
||||||
|
? `${pathInfo.hostname}:${pathInfo.port}`
|
||||||
|
: pathInfo.hostname;
|
||||||
|
|
||||||
|
// check whether extracted path info matches the current origin
|
||||||
|
login.inCurrentHost = origin.hostname === pathInfo.hostname;
|
||||||
|
|
||||||
|
// check whether the current origin is subordinate to extracted path info, meaning:
|
||||||
|
// - that the path info is not a single level domain (e.g. com, net, local)
|
||||||
|
// - and that the current origin is a subdomain of that path info
|
||||||
|
if (
|
||||||
|
pathInfo.hostname.includes(".") &&
|
||||||
|
origin.hostname.endsWith(`.${pathInfo.hostname}`)
|
||||||
|
) {
|
||||||
|
login.inCurrentHost = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter out entries with a non-matching port
|
||||||
|
if (pathInfo.port && pathInfo.port !== origin.port) {
|
||||||
|
login.inCurrentHost = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
login.host = null;
|
||||||
|
login.inCurrentHost = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update recent counter
|
||||||
login.recent =
|
login.recent =
|
||||||
settings.recent[sha1(settings.host + sha1(login.store.id + sha1(login.login)))];
|
settings.recent[sha1(settings.origin + sha1(login.store.id + sha1(login.login)))];
|
||||||
if (!login.recent) {
|
if (!login.recent) {
|
||||||
login.recent = {
|
login.recent = {
|
||||||
when: 0,
|
when: 0,
|
||||||
@@ -124,7 +153,7 @@ function filterSortLogins(logins, searchQuery, currentDomainOnly) {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
var remainingInCurrentDomain = candidates.filter(
|
var remainingInCurrentDomain = candidates.filter(
|
||||||
login => login.inCurrentDomain && !login.recent.count
|
login => login.inCurrentHost && !login.recent.count
|
||||||
);
|
);
|
||||||
candidates = recent.concat(remainingInCurrentDomain);
|
candidates = recent.concat(remainingInCurrentDomain);
|
||||||
}
|
}
|
||||||
|
@@ -15,14 +15,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@browserpass/url": "^1.1.3",
|
||||||
"chrome-extension-async": "^3.3.2",
|
"chrome-extension-async": "^3.3.2",
|
||||||
"fuzzysort": "^1.1.4",
|
"fuzzysort": "^1.1.4",
|
||||||
"idb": "^4.0.3",
|
"idb": "^4.0.3",
|
||||||
"ignore": "^5.1.4",
|
"ignore": "^5.1.4",
|
||||||
"mithril": "^1.1.0",
|
"mithril": "^1.1.0",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"sha1": "^1.1.1",
|
"sha1": "^1.1.1"
|
||||||
"tldjs": "^2.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserify": "^16.2.3",
|
"browserify": "^16.2.3",
|
||||||
|
@@ -47,7 +47,7 @@ async function run() {
|
|||||||
throw new Error(settings.hostError.params.message);
|
throw new Error(settings.hostError.params.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof settings.host === "undefined") {
|
if (typeof settings.origin === "undefined") {
|
||||||
throw new Error("Unable to retrieve current tab information");
|
throw new Error("Unable to retrieve current tab information");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,6 +29,7 @@ function SearchInterface(popup) {
|
|||||||
*/
|
*/
|
||||||
function view(ctl, params) {
|
function view(ctl, params) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var host = new URL(this.popup.settings.origin).host;
|
||||||
return m(
|
return m(
|
||||||
"form.part.search",
|
"form.part.search",
|
||||||
{
|
{
|
||||||
@@ -59,7 +60,7 @@ function view(ctl, params) {
|
|||||||
[
|
[
|
||||||
this.popup.currentDomainOnly
|
this.popup.currentDomainOnly
|
||||||
? m("div.hint.badge", [
|
? m("div.hint.badge", [
|
||||||
this.popup.settings.host,
|
host,
|
||||||
m("div.remove-hint", {
|
m("div.remove-hint", {
|
||||||
onclick: function(e) {
|
onclick: function(e) {
|
||||||
var target = document.querySelector(
|
var target = document.querySelector(
|
||||||
|
@@ -2,6 +2,13 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@browserpass/url@^1.1.3":
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@browserpass/url/-/url-1.1.3.tgz#8b94896198e7032b80c7d82e836d59e42b769669"
|
||||||
|
integrity sha512-XWhYNZo0BGcyFxkum8g1DeUkFw9QyqcaRTOEinyPSpIubh+aL2VQxbdItitVtS5qkkowqaA5Xt1H2pAqu8ic8w==
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.1.1"
|
||||||
|
|
||||||
JSONStream@^1.0.3:
|
JSONStream@^1.0.3:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
|
||||||
@@ -1089,7 +1096,7 @@ punycode@^1.3.2, punycode@^1.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0, punycode@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
@@ -1347,13 +1354,6 @@ timers-browserify@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
process "~0.11.0"
|
process "~0.11.0"
|
||||||
|
|
||||||
tldjs@^2.3.0:
|
|
||||||
version "2.3.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tldjs/-/tldjs-2.3.1.tgz#cf09c3eb5d7403a9e214b7d65f3cf9651c0ab039"
|
|
||||||
integrity sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==
|
|
||||||
dependencies:
|
|
||||||
punycode "^1.4.1"
|
|
||||||
|
|
||||||
to-arraybuffer@^1.0.0:
|
to-arraybuffer@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||||
|
Reference in New Issue
Block a user