diff --git a/src/index.ts b/src/index.ts index 00c8b3e..4dd7d80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,8 +47,9 @@ export const SERVER = { }, }; -const DONATE_URL = 'https://organicmaps.app/donate/'; -const DONATE_URL_RU = 'https://organicmaps.app/ru/donate/'; +// Exported for tests. +export const DONATE_URL = 'https://organicmaps.app/donate/'; +export const DONATE_URL_RU = 'https://organicmaps.app/ru/donate/'; // Main entry point. addEventListener('fetch', (event) => { @@ -119,8 +120,9 @@ export async function handleRequest(request: Request) { servers: servers, }; - // Disable donates for Google reviewers for all google app versions AFTER this one. + // Disable donates for reviewers for all app versions AFTER this one. const lastApprovedAndReleasedGoogleAppVersionCode = 221102; + const lastApprovedAndReleasediOSAppVersionCode = 221120; let donatesEnabled = true; if ( appVersion.flavor === 'google' && @@ -128,6 +130,14 @@ export async function handleRequest(request: Request) { appVersion.code > lastApprovedAndReleasedGoogleAppVersionCode) ) { donatesEnabled = false; + } else if (appVersion.build === undefined) { + // Disable donates for older iOS versions without donates menu support. + donatesEnabled = false; + } else if ( + (appVersion.flavor === 'ios' && (request.cf?.asOrganization || '').toLowerCase().includes('apple')) || + appVersion.code > lastApprovedAndReleasediOSAppVersionCode + ) { + donatesEnabled = false; } if (donatesEnabled) { diff --git a/src/versions.ts b/src/versions.ts index 1d33539..5d565f8 100644 --- a/src/versions.ts +++ b/src/versions.ts @@ -13,37 +13,46 @@ export function parseDataVersion(strDataVersion: string | null): number | null { return dataVersion; } -const VERSION_RE = new RegExp('(\\d{4}).(\\d{1,2}).(\\d{1,2})-(\\d{1,9})(?:-([^-]+))?'); +// 2022.11.20 for iOS versions released before November 21 (without donate menu) +// 2022.11.24-4-ios for newer iOS versions (with donate menu) +// 2022.12.24-10-Google for Android +const VERSION_RE = /(\d{4}).(\d{1,2}).(\d{1,2})(?:$|-(\d{1,9})(?:-([^-]+))?)/; +// Returns code like 221224 for both platforms, build and flavor for Android and newer iOS versions. export function parseAppVersion( versionName: string | null, -): { code: number; build: number | undefined; flavor: string | undefined } | null { +): { code: number; build?: number; flavor?: string | undefined } | null { if (!versionName) { return null; } const m = versionName.match(VERSION_RE); - if (m === null || m.length < 6) { + if (m === null || m.length < 4) { 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 - ) { + if (Number.isNaN(yyyy) || yyyy > 2099 || yyyy < 2022) { return null; } + const mm = parseInt(m[2]); + if (Number.isNaN(mm) || mm > 12 || mm < 1) { + return null; + } + const dd = parseInt(m[3]); + if (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')); + // Older iOS versions without donate button. + if (m[4] === undefined) { + return { code: code }; + } + + const buildNumber = parseInt(m[4]); + const build = Number.isNaN(buildNumber) ? 0 : buildNumber; + // 'ios' for iOS devices. + const flavor = (m[5] !== undefined && m[5].toLowerCase()) || undefined; + return { code: code, flavor: flavor, diff --git a/test/index.test.ts b/test/index.test.ts index a33b12d..350b918 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,17 +1,19 @@ import { describe, expect, test } from '@jest/globals'; -import { handleRequest, SERVER } from '../src/index'; +import { handleRequest, SERVER, DONATE_URL, DONATE_URL_RU } 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 () => { +// Note: CF lowercases all headers. +describe('X-OM-DataVersion', () => { + test('no X-OM-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 () => { + + test('has X-OM-DataVersion', async () => { const server = SERVER.fi1; let req = new Request(URL, { headers: { @@ -22,7 +24,8 @@ describe('old versions', () => { expect(result.status).toBe(200); expect(JSON.parse(await result.text())).toContain(server.url); }); - test('default routing to planet', async () => { + + test('Default routing to planet', async () => { let req = new Request(URL, { headers: { 'X-OM-DataVersion': '210000', // this version doesn't exist on servers @@ -33,3 +36,75 @@ describe('old versions', () => { expect(JSON.parse(await result.text())).toEqual([SERVER.planet.url]); }); }); + +describe('X-OM-AppVersion DonateUrl', () => { + test('Old versions without X-OM-AppVersion and old metaserver JSON format', async () => { + const req = new Request(URL); + const response = await handleRequest(req); + expect(response.status).toBe(200); + expect(JSON.parse(await response.text())).toEqual([SERVER.backblaze.url]); + }); + + const server = SERVER.fi1; + + test('Newer metaserver JSON format with donates support', async () => { + let req = new Request(URL, { + headers: { + 'X-OM-AppVersion': '2022.08.23-1-Google', + 'X-OM-DataVersion': String(server.dataVersions[0]), + }, + }); + const response = await handleRequest(req); + expect(response.status).toBe(200); + const result = JSON.parse(await response.text()); + expect(result.servers).toBeDefined(); + expect(result.servers.length).toBeGreaterThan(0); + expect(result.servers).toContain(server.url); + expect(result.settings).toBeDefined(); + expect(result.settings.DonateUrl).toBeDefined(); + expect(result.settings.DonateUrl).toEqual(DONATE_URL); + }); + + test('Newer metaserver JSON format with donates support', async () => { + let req = new Request(URL, { + headers: { + 'X-OM-AppVersion': '2022.08.23-1-Google', + 'X-OM-DataVersion': String(server.dataVersions[0]), + }, + //@ts-ignore + cf: { country: 'RU' }, + }); + const response = await handleRequest(req); + expect(response.status).toBe(200); + const result = JSON.parse(await response.text()); + expect(result.settings.DonateUrl).toBeDefined(); + expect(result.settings.DonateUrl).toEqual(DONATE_URL_RU); + }); + + test('Older iOS versions with X-OM-AppVersion but without donates', async () => { + let req = new Request(URL, { + headers: { + 'X-OM-AppVersion': '2022.11.20', + 'X-OM-DataVersion': String(server.dataVersions[0]), + }, + }); + const response = await handleRequest(req); + expect(response.status).toBe(200); + const result = JSON.parse(await response.text()); + expect(result.settings).not.toBeDefined(); + }); + + test('Newer iOS versions with donate menu support', async () => { + let req = new Request(URL, { + headers: { + 'X-OM-AppVersion': '2022.11.20-4-ios', + 'X-OM-DataVersion': String(server.dataVersions[0]), + }, + }); + const response = await handleRequest(req); + expect(response.status).toBe(200); + const result = JSON.parse(await response.text()); + expect(result.settings.DonateUrl).toBeDefined(); + expect(result.settings.DonateUrl).toEqual(DONATE_URL); + }); +}); diff --git a/test/versions.test.ts b/test/versions.test.ts index b4ddb42..39424ae 100644 --- a/test/versions.test.ts +++ b/test/versions.test.ts @@ -20,6 +20,10 @@ describe('parseDataVersion', () => { describe('parseAppVersion', () => { const tests: { [key: string]: object | null } = { + // Older iOS releases without donate menu + '2022.08.01': { code: 220801 }, + // Newer iOS releases with donate menu + '2022.11.25-5-ios': { code: 221125, build: 5, flavor: 'ios' }, '2022.08.01-1': { code: 220801, build: 1 }, '2022.08.01-1-Google': { code: 220801, build: 1, flavor: 'google' }, // -debug is ignored @@ -32,8 +36,20 @@ describe('parseAppVersion', () => { '2022.13.31-1': null, '2022.01.00-1': null, '2022.01.32-1': null, + '22.01.31-1': null, + '22.01.31': null, + '22.01.31-3-flavor': null, '202.01.31-1': null, + '202.01.312-1': null, + '202.01.312': null, + '202.01.31-aa-flavor': null, + '202.01.31-5a-flavor': null, + '202.01.31-a5-flavor': null, '2022..31-11': null, + '2022..31-11-flavor': null, + '.11.31-11': null, + '.11.31-11-flavor': null, + '.11..31-11-flavor': null, garbage: null, '': null, null: null,