Implement conditional donates
This commit is contained in:
parent
ddd4dea2f3
commit
7d8797c56a
9 changed files with 7563 additions and 25 deletions
3
.github/workflows/check.yml
vendored
3
.github/workflows/check.yml
vendored
|
@ -29,3 +29,6 @@ jobs:
|
|||
|
||||
- name: Format
|
||||
run: npm run format:ci
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
|
19
jest.config.json
Normal file
19
jest.config.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"preset": "ts-jest/presets/default-esm",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"tsconfig": "./tsconfig.json",
|
||||
"useESM": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"testRegex": "/test/.*\\.test\\.ts$",
|
||||
"testEnvironment": "miniflare",
|
||||
"testEnvironmentOptions": {
|
||||
"scriptPath": "./src/index.ts",
|
||||
"modules": true
|
||||
},
|
||||
"collectCoverageFrom": ["src/**/*.{ts,tsx}"]
|
||||
}
|
7348
package-lock.json
generated
7348
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -6,7 +6,7 @@
|
|||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "esbuild src/index.ts --bundle --outfile=dist/index.js",
|
||||
"test": "eslint src/**/*.ts && tsc --noEmit",
|
||||
"test": "jest",
|
||||
"lint": "eslint --fix --ext .tsx,.ts src/",
|
||||
"lint:ci": "eslint --ext .tsx,.ts src/ --max-warnings 0",
|
||||
"format": "prettier --write '{src,test}/**/*.{ts,tsx}' '*.json' '*.yml' '*.toml' '.github/**/*.yml'",
|
||||
|
@ -17,12 +17,18 @@
|
|||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^3.3.1",
|
||||
"@cloudflare/wrangler": "^1.19.8",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.11.0",
|
||||
"@typescript-eslint/parser": "^5.11.0",
|
||||
"esbuild": "^0.15.7",
|
||||
"eslint": "^8.9.0",
|
||||
"jest": "^29.0.3",
|
||||
"jest-environment-miniflare": "^2.8.2",
|
||||
"miniflare": "^2.8.2",
|
||||
"prettier": "^2.5.1",
|
||||
"prettier-plugin-toml": "^0.3.1",
|
||||
"typescript": "^4.5.5"
|
||||
"ts-jest": "^29.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"wrangler": "^2.0.29"
|
||||
}
|
||||
}
|
||||
|
|
83
src/index.ts
83
src/index.ts
|
@ -1,8 +1,10 @@
|
|||
export {};
|
||||
|
||||
import { parseDataVersion, parseAppVersion } from './utils';
|
||||
|
||||
// TODO: Implement automated version checks from this metaserver script.
|
||||
// It should check by cron if actual files are really available on all servers.
|
||||
const SERVER = {
|
||||
export const SERVER = {
|
||||
backblaze: {
|
||||
// BackBlaze + CloudFlare (US-West) unmetered.
|
||||
url: 'https://cdn-us1.organicmaps.app/',
|
||||
|
@ -43,27 +45,15 @@ const SERVER = {
|
|||
},
|
||||
};
|
||||
|
||||
const DONATE_URL = 'https://donate.organicmaps.app';
|
||||
const DONATE_URL_RU = 'https://donate.organicmaps.ru';
|
||||
|
||||
// Main entry point.
|
||||
addEventListener('fetch', (event) => {
|
||||
event.respondWith(handleRequest(event.request).catch((err) => new Response(err.stack, { status: 500 })));
|
||||
});
|
||||
|
||||
// Starting from September release, our clients have 'X-OM-DataVersion' header with the value
|
||||
// of their current maps data version, for example, "211022" (October 22, 2021).
|
||||
// It is lowercased by Cloudflare.
|
||||
// Returns 0 if data version is absent or invalid, or a valid integer version.
|
||||
function extractDataVersion(request: Request): number {
|
||||
const strDataVersion = request.headers.get('x-om-dataversion');
|
||||
if (strDataVersion) {
|
||||
const dataVersion = parseInt(strDataVersion);
|
||||
if (!Number.isNaN(dataVersion) && dataVersion >= 210000 && dataVersion <= 500000) {
|
||||
return dataVersion;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function handleRequest(request: Request) {
|
||||
export async function handleRequest(request: Request) {
|
||||
const { pathname } = new URL(request.url);
|
||||
|
||||
switch (pathname) {
|
||||
|
@ -72,10 +62,13 @@ async function handleRequest(request: Request) {
|
|||
case '/servers': {
|
||||
// Private for map files.
|
||||
let servers;
|
||||
const dataVersion = extractDataVersion(request);
|
||||
if (dataVersion == 0) {
|
||||
// Starting from 2021-09, our clients have 'X-OM-DataVersion' header with the value
|
||||
// of their current maps data version, for example, "211022" (October 22, 2021).
|
||||
// It is lowercased by Cloudflare.
|
||||
const dataVersion = parseDataVersion(request.headers.get('x-om-dataversion'));
|
||||
if (dataVersion === null) {
|
||||
servers = [SERVER.backblaze];
|
||||
} else
|
||||
} else {
|
||||
switch (request.cf?.continent) {
|
||||
// See https://developers.cloudflare.com/firewall/cf-firewall-language/fields for a list of all continents.
|
||||
case 'NA': // North America
|
||||
|
@ -101,8 +94,56 @@ async function handleRequest(request: Request) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
servers = servers.map((server) => server.url);
|
||||
|
||||
const appVersion = parseAppVersion(request.headers.get('x-om-appversion'));
|
||||
if (!appVersion) {
|
||||
// Old format for <220823
|
||||
return new Response(JSON.stringify(servers), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
// New format for >=220823
|
||||
const response: {
|
||||
servers: string[];
|
||||
settings?: {
|
||||
DonateUrl?: string;
|
||||
};
|
||||
} = {
|
||||
servers: servers,
|
||||
};
|
||||
|
||||
// Donates
|
||||
let donatesEnabled = false;
|
||||
if (appVersion.flavor == 'fdroid' || appVersion.flavor == 'web') {
|
||||
donatesEnabled = true;
|
||||
} else if (appVersion.flavor == 'huawei') {
|
||||
donatesEnabled = true;
|
||||
} else if (
|
||||
appVersion.flavor == 'google' &&
|
||||
!(request.cf?.asOrganization || '').toLowerCase().includes('google') &&
|
||||
(request.cf?.country == 'DE' ||
|
||||
request.cf?.country == 'TR' ||
|
||||
request.cf?.country == 'CY' ||
|
||||
request.cf?.country == 'CH')
|
||||
) {
|
||||
donatesEnabled = true;
|
||||
}
|
||||
|
||||
if (donatesEnabled) {
|
||||
if (request.cf?.country == 'RU') {
|
||||
response.settings = {
|
||||
DonateUrl: DONATE_URL_RU,
|
||||
};
|
||||
} else {
|
||||
response.settings = {
|
||||
DonateUrl: DONATE_URL,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const response = servers.map((server) => server.url);
|
||||
return new Response(JSON.stringify(response), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
|
52
src/utils.ts
Normal file
52
src/utils.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
export {};
|
||||
|
||||
export function parseDataVersion(strDataVersion: string | null): number | null {
|
||||
if (!strDataVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dataVersion = parseInt(strDataVersion);
|
||||
if (Number.isNaN(dataVersion) || dataVersion < 210000 || dataVersion > 500000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dataVersion;
|
||||
}
|
||||
|
||||
const VERSION_RE = new RegExp('(\\d{4}).(\\d{1,2}).(\\d{1,2})-(\\d{1,9})(?:-([^-]+))?');
|
||||
export function parseAppVersion(
|
||||
versionName: string | null,
|
||||
): { code: number; build: number | undefined; flavor: string | undefined } | null {
|
||||
if (!versionName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const m = versionName.match(VERSION_RE);
|
||||
if (m === null || m.length < 6) {
|
||||
return null;
|
||||
}
|
||||
const yyyy = parseInt(m[1]);
|
||||
const mm = parseInt(m[2]);
|
||||
const dd = parseInt(m[3]);
|
||||
const build = Number.isNaN(parseInt(m[4])) ? undefined : parseInt(m[4]);
|
||||
const flavor = (m[5] !== undefined && m[5].toLowerCase()) || undefined;
|
||||
if (
|
||||
Number.isNaN(yyyy) ||
|
||||
yyyy > 2099 ||
|
||||
yyyy < 2022 ||
|
||||
Number.isNaN(mm) ||
|
||||
mm > 12 ||
|
||||
mm < 1 ||
|
||||
Number.isNaN(dd) ||
|
||||
dd > 31 ||
|
||||
dd < 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const code = parseInt(String(yyyy % 100) + String(mm).padStart(2, '0') + String(dd).padStart(2, '0'));
|
||||
return {
|
||||
code: code,
|
||||
flavor: flavor,
|
||||
build: build,
|
||||
};
|
||||
}
|
35
test/index.test.ts
Normal file
35
test/index.test.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { describe, expect, test } from '@jest/globals';
|
||||
import { handleRequest, SERVER } from '../src/index';
|
||||
|
||||
const URL = 'https://worker/servers';
|
||||
const LAST_DATA_VERSION = SERVER.planet.dataVersions[SERVER.planet.dataVersions.length - 1];
|
||||
|
||||
describe('old versions', () => {
|
||||
test('no dataVersion', async () => {
|
||||
const req = new Request(URL);
|
||||
const result = await handleRequest(req);
|
||||
expect(result.status).toBe(200);
|
||||
expect(JSON.parse(await result.text())).toEqual([SERVER.backblaze.url]);
|
||||
});
|
||||
test('has dataVersion', async () => {
|
||||
const server = SERVER.fi1;
|
||||
let req = new Request(URL, {
|
||||
headers: {
|
||||
'X-OM-DataVersion': String(server.dataVersions[0]),
|
||||
},
|
||||
});
|
||||
const result = await handleRequest(req);
|
||||
expect(result.status).toBe(200);
|
||||
expect(JSON.parse(await result.text())).toContain(server.url);
|
||||
});
|
||||
test('default routing to planet', async () => {
|
||||
let req = new Request(URL, {
|
||||
headers: {
|
||||
'X-OM-DataVersion': '210000', // this version doesn't exist on servers
|
||||
},
|
||||
});
|
||||
const result = await handleRequest(req);
|
||||
expect(result.status).toBe(200);
|
||||
expect(JSON.parse(await result.text())).toEqual([SERVER.planet.url]);
|
||||
});
|
||||
});
|
36
test/utils.test.ts
Normal file
36
test/utils.test.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { describe, expect, test } from '@jest/globals';
|
||||
import { parseDataVersion, parseAppVersion } from '../src/utils';
|
||||
|
||||
describe('parseDataVersion', () => {
|
||||
test('220801', () => expect(parseDataVersion('220801')).toEqual(220801));
|
||||
test('210000', () => expect(parseDataVersion('210000')).toEqual(210000));
|
||||
test('500000', () => expect(parseDataVersion('500000')).toEqual(500000));
|
||||
test('200000', () => expect(parseDataVersion('200000')).toEqual(null));
|
||||
test('500001', () => expect(parseDataVersion('500001')).toEqual(null));
|
||||
test('garbage', () => expect(parseDataVersion('garbage')).toEqual(null));
|
||||
test('', () => expect(parseDataVersion('')).toEqual(null));
|
||||
test('', () => expect(parseDataVersion(null)).toEqual(null));
|
||||
});
|
||||
|
||||
describe('parseAppVersion', () => {
|
||||
test('2022.08.01-1', () => expect(parseAppVersion('2022.08.01-1')).toEqual({ code: 220801, build: 1 }));
|
||||
test('2022.08.01-1-Google', () =>
|
||||
expect(parseAppVersion('2022.08.01-1-Google')).toEqual({ code: 220801, build: 1, flavor: 'google' }));
|
||||
// -debug is ignored
|
||||
test('2022.08.01-1-Google-debug', () =>
|
||||
expect(parseAppVersion('2022.08.01-1-Google-debug')).toEqual({ code: 220801, build: 1, flavor: 'google' }));
|
||||
test('2022.1.1-0', () => expect(parseAppVersion('2022.1.2-0')).toEqual({ code: 220102, build: 0 }));
|
||||
test('2099.12.31-999999999', () =>
|
||||
expect(parseAppVersion('2099.12.31-999999999')).toEqual({ code: 991231, build: 999999999 }));
|
||||
test('2021.01.31-1', () => expect(parseAppVersion('2021.01.31-1')).toEqual(null));
|
||||
test('2100.01.31-1', () => expect(parseAppVersion('2100.01.31-1')).toEqual(null));
|
||||
test('2022.00.31-1', () => expect(parseAppVersion('2022.00.31-1')).toEqual(null));
|
||||
test('2022.13.31-1', () => expect(parseAppVersion('2022.13.31-1')).toEqual(null));
|
||||
test('2022.01.00-1', () => expect(parseAppVersion('2022.01.00-1')).toEqual(null));
|
||||
test('2022.01.32-1', () => expect(parseAppVersion('2022.01.32-1')).toEqual(null));
|
||||
test('202.01.31-1', () => expect(parseAppVersion('2100.01.31-1')).toEqual(null));
|
||||
test('2022..31-11', () => expect(parseAppVersion('2100.01.31-1')).toEqual(null));
|
||||
test('garbage', () => expect(parseAppVersion('garbage')).toEqual(null));
|
||||
test('', () => expect(parseAppVersion('')).toEqual(null));
|
||||
test('null', () => expect(parseAppVersion('null')).toEqual(null));
|
||||
});
|
|
@ -9,7 +9,7 @@ workers_dev = true
|
|||
|
||||
[build]
|
||||
upload.format = 'service-worker'
|
||||
command = 'npm i --prefer-offline --no-audit && npm run build'
|
||||
command = 'npm ci --prefer-offline --no-audit && npm run build'
|
||||
|
||||
[vars]
|
||||
DEBUG = true
|
||||
|
|
Loading…
Add table
Reference in a new issue