Skip to content

Commit

Permalink
AG-15591 unite fallback info together
Browse files Browse the repository at this point in the history
Merge in EXTENSIONS/vpn-extension from fix/AG-15591-2 to master

Squashed commit of the following:

commit 68c60c1
Author: Maxim Topciu <mtopciu@adguard.com>
Date:   Mon Aug 8 12:51:27 2022 +0300

    AG-15591 use default api urls if bkp returned none

commit 20499ad
Author: Maxim Topciu <mtopciu@adguard.com>
Date:   Fri Aug 5 22:33:46 2022 +0300

    AG-15591 unite fallback info together
  • Loading branch information
maximtop committed Aug 9, 2022
1 parent e2af327 commit 2414ea3
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 116 deletions.
25 changes: 0 additions & 25 deletions src/background/api/apiUrlCache.ts

This file was deleted.

153 changes: 87 additions & 66 deletions src/background/api/fallbackApi.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import axios from 'axios';
import { log } from '../../lib/logger';
import { clearFromWrappingQuotes } from '../../lib/helpers';

import { AUTH_API_URL, VPN_API_URL, STAGE_ENV } from '../config';
import { apiUrlCache } from './apiUrlCache';
import { clearFromWrappingQuotes } from '../../lib/string-utils';
import { log } from '../../lib/logger';

export const DEFAULT_CACHE_EXPIRE_TIME_MS = 1000 * 60 * 5; // 5 minutes

// DNS over https api
const GOOGLE_DOH_HOSTNAME = 'dns.google';
Expand All @@ -27,24 +29,64 @@ const DEFAULT_COUNTRY_INFO = { country: 'none', bkp: true };

const REQUEST_TIMEOUT_MS = 3 * 1000;

export const VPN_API_URL_KEY = 'vpn';
export const AUTH_API_URL_KEY = 'auth';
type CountryInfo = {
country: string;
bkp: boolean;
};

export class FallbackApi {
type FallbackInfo = {
vpnApiUrl: string;

authApiUrl: string;
countryInfo: CountryInfo;
expiresInMs: number;
};

export class FallbackApi {
/**
* We keep all fallback info in one object, because it's easier to work with
* Also, vpnApiUrl and authApiUrl always are updated in pairs
*/
fallbackInfo: FallbackInfo;

/**
* Here we save default apn api url, it may be used if doh returns none result
*/
defaultVpnApiUrl: string;

/**
* Here we save default auth api url, it may be used if doh returns none result
*/
defaultAuthApiUrl: string;

/**
* Default urls we set already expired,
* so we need to check bkp url immediately when bkp url is required
*/
constructor(vpnApiUrl: string, authApiUrl: string) {
this.setVpnApiUrl(vpnApiUrl, Date.now());
this.setAuthApiUrl(authApiUrl, Date.now());
this.defaultVpnApiUrl = vpnApiUrl;
this.defaultAuthApiUrl = authApiUrl;

this.setFallbackInfo({
vpnApiUrl,
authApiUrl,
countryInfo: DEFAULT_COUNTRY_INFO,
expiresInMs: Date.now() - 1,
});
}

async init() {
public async init(): Promise<void> {
await this.updateFallbackInfo();
}

private setFallbackInfo(fallbackInfo: FallbackInfo) {
this.fallbackInfo = fallbackInfo;
}

private static needsUpdate(fallbackInfo: FallbackInfo): boolean {
return fallbackInfo.expiresInMs < Date.now();
}

private async updateFallbackInfo() {
const countryInfo = await this.getCountryInfo();
const localStorageBkp = this.getLocalStorageBkp();

Expand All @@ -57,49 +99,50 @@ export class FallbackApi {
this.getBkpAuthApiUrl(countryInfo.country),
]);

if (bkpVpnApiUrl) {
this.setVpnApiUrl(bkpVpnApiUrl);
}

if (bkpAuthApiUrl) {
this.setAuthApiUrl(bkpAuthApiUrl);
if (bkpVpnApiUrl && bkpAuthApiUrl) {
this.setFallbackInfo({
vpnApiUrl: bkpVpnApiUrl,
authApiUrl: bkpAuthApiUrl,
countryInfo,
expiresInMs: Date.now() + DEFAULT_CACHE_EXPIRE_TIME_MS,
});
}
}

public getVpnApiUrl = async () => {
if (apiUrlCache.needsUpdate(VPN_API_URL_KEY)) {
await this.updateVpnApiUrl();
return this.vpnApiUrl;
private async getFallbackInfo(): Promise<FallbackInfo> {
if (FallbackApi.needsUpdate(this.fallbackInfo)) {
await this.updateFallbackInfo();
}

return this.vpnApiUrl;
};
return this.fallbackInfo;
}

public getAuthApiUrl = async () => {
if (apiUrlCache.needsUpdate(AUTH_API_URL_KEY)) {
await this.updateAuthApiUrl();
return this.authApiUrl;
}
public getVpnApiUrl = async (): Promise<string> => {
const fallbackInfo = await this.getFallbackInfo();
return fallbackInfo.vpnApiUrl;
};

return this.authApiUrl;
public getAuthApiUrl = async (): Promise<string> => {
const fallbackInfo = await this.getFallbackInfo();
return fallbackInfo.authApiUrl;
};

public getAuthBaseUrl = async () => {
public getAuthBaseUrl = async (): Promise<string> => {
const authApiUrl = await this.getAuthApiUrl();
return `${authApiUrl}/oauth/authorize`;
};

public getAuthRedirectUri = async () => {
public getAuthRedirectUri = async (): Promise<string> => {
const authApiUrl = await this.getAuthApiUrl();
return `${authApiUrl}/oauth.html?adguard-vpn=1`;
};

public getAccountApiUrl = async () => {
public getAccountApiUrl = async (): Promise<string> => {
const vpnApiUrl = await this.getVpnApiUrl();
return `${vpnApiUrl}/account`;
};

public getApiUrlsExclusions = async () => {
public getApiUrlsExclusions = async (): Promise<string[]> => {
return [
await this.getVpnApiUrl(),
await this.getAuthApiUrl(),
Expand All @@ -110,37 +153,10 @@ export class FallbackApi {
].map((url) => `*${url}`);
};

private setVpnApiUrl = (url: string, expireInMs?: number) => {
this.vpnApiUrl = url;
apiUrlCache.set(VPN_API_URL_KEY, this.vpnApiUrl, expireInMs);
};

private setAuthApiUrl = (url: string, expiresInMs?: number) => {
this.authApiUrl = url;
apiUrlCache.set(AUTH_API_URL_KEY, this.authApiUrl, expiresInMs);
};

private async updateVpnApiUrl() {
const { country } = await this.getCountryInfo();
const bkpUrl = await this.getBkpVpnApiUrl(country);
if (bkpUrl) {
this.setVpnApiUrl(bkpUrl);
}
}

private async updateAuthApiUrl() {
const { country } = await this.getCountryInfo();
const bkpUrl = await this.getBkpAuthApiUrl(country);
if (bkpUrl) {
this.setAuthApiUrl(bkpUrl);
}
}

/**
* Gets bkp flag value from local storage, used for testing purposes
* @return {boolean}
*/
private getLocalStorageBkp = () => {
private getLocalStorageBkp = (): boolean => {
const storedBkp = localStorage.getItem(BKP_KEY);
let localStorageBkp = Number.parseInt(String(storedBkp), 10);

Expand All @@ -149,7 +165,7 @@ export class FallbackApi {
return !!localStorageBkp;
};

private getCountryInfo = async () => {
private getCountryInfo = async (): Promise<CountryInfo> => {
try {
const { data: { country, bkp } } = await axios.get(
`https://${WHOAMI_URL}`,
Expand Down Expand Up @@ -240,9 +256,6 @@ export class FallbackApi {
this.getBkpUrlByAliDnsDoh(hostname),
]);
bkpUrl = clearFromWrappingQuotes(bkpUrl);
if (bkpUrl === EMPTY_BKP_URL) {
bkpUrl = null;
}
} catch (e) {
log.error(e);
bkpUrl = null;
Expand All @@ -251,15 +264,23 @@ export class FallbackApi {
return bkpUrl;
};

private getBkpVpnApiUrl = async (country: string) => {
getBkpVpnApiUrl = async (country: string) => {
const hostname = `${country.toLowerCase()}.${BKP_API_HOSTNAME_PART}`;
const bkpApiUrl = await this.getBkpUrl(hostname);
if (bkpApiUrl === EMPTY_BKP_URL) {
return this.defaultVpnApiUrl;
}
return bkpApiUrl;
};

private getBkpAuthApiUrl = async (country: string) => {
getBkpAuthApiUrl = async (country: string) => {
const hostname = `${country.toLowerCase()}.${BKP_AUTH_HOSTNAME_PART}`;

const bkpAuthUrl = await this.getBkpUrl(hostname);
if (bkpAuthUrl === EMPTY_BKP_URL) {
return this.defaultAuthApiUrl;
}

return bkpAuthUrl;
};

Expand Down
4 changes: 0 additions & 4 deletions src/lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,3 @@ export const runWithCancel = (fn, ...args) => {

return { promise, cancel };
};

export const clearFromWrappingQuotes = (str) => {
return str.replace(/^"|"$/g, '');
};
28 changes: 16 additions & 12 deletions src/lib/string-utils.js → src/lib/string-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-useless-escape */
/**
* Renders string templates
*
Expand All @@ -9,9 +8,8 @@
*
* @param {string} template
* @param {object} tags
* @returns {string}
*/
export const renderTemplate = (template, tags) => {
export const renderTemplate = (template: string, tags: { [key: string]: string }): string => {
return Object.entries(tags).reduce((acc, [key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
return acc.replace(regex, value);
Expand All @@ -23,17 +21,16 @@ export const renderTemplate = (template, tags) => {
* @param str
* @returns {!Uint8Array}
*/
export const stringToUint8Array = (str) => {
return new TextEncoder('utf-8').encode(str);
export const stringToUint8Array = (str: string): Uint8Array => {
return new TextEncoder().encode(str);
};

/**
* Compares if two hostnames w/ or w/o www are equal
* @param hostnameA
* @param hostnameB
* @returns {boolean}
*/
export const areHostnamesEqual = (hostnameA, hostnameB) => {
export const areHostnamesEqual = (hostnameA: string, hostnameB: string): boolean => {
const wwwRegex = /^www\./;
const oldHostnameWithoutWww = hostnameA.replace(wwwRegex, '');
const newHostnameWithoutWww = hostnameB.replace(wwwRegex, '');
Expand All @@ -45,9 +42,8 @@ export const areHostnamesEqual = (hostnameA, hostnameB) => {
* IMPORTANT - note that regexp asserts position at the end of the string
* @param {string} url - hostname or url
* @param {string} pattern
* @returns {boolean}
*/
export const shExpMatch = (url, pattern) => {
export const shExpMatch = (url: string, pattern: string): boolean => {
let regexpStr = pattern.replace(/\./g, '\\.');
regexpStr = regexpStr.replace(/\*/g, '.*');
const regexp = new RegExp(`^${regexpStr}$`);
Expand All @@ -57,9 +53,8 @@ export const shExpMatch = (url, pattern) => {
/**
* Checks if string is valid url with http: or https: protocol
* @param {string} str
* @returns {boolean}
*/
export const isHttp = (str) => {
export const isHttp = (str: string): boolean => {
let url;
try {
url = new URL(str);
Expand All @@ -72,9 +67,18 @@ export const isHttp = (str) => {
/**
* Checks if provided string is valid exclusion
*/
export const isValidExclusion = (exclusion) => {
export const isValidExclusion = (exclusion: string): boolean => {
// Regexp validates simple domains and exclusions with wildcard
// e.g "*.example.org", "example.org", more cases can be found in tests
const VALID_EXCLUSION_REGEX = /^((\*\.)?(\*|[\w\u0400-\u04FF][\w_\-.\u0400-\u04FF\u00FC]*)\.([a-z\u0400-\u04FF]{2,8}|\*)|(25[0-5]|2[0-4]\d|(1\d{2})|[1-9]\d|[1-9])\.((25[0-5]|2[0-4]\d|(1\d{2})|(\d{1,2}))\.){2}(25[0-5]|2[0-4]\d|(1\d{2})|(\d{1,2})))[^\s]*$/;
return VALID_EXCLUSION_REGEX.test(exclusion);
};

/**
* Clears string from wrapping quotes
* @param str
* @returns {*}
*/
export const clearFromWrappingQuotes = (str: string): string => {
return str.replace(/^"|"$/g, '');
};
Loading

0 comments on commit 2414ea3

Please sign in to comment.