116 lines
11 KiB
TypeScript
116 lines
11 KiB
TypeScript
// Absolute deep links to App Store Connect and Play Console pages. Every message that sends the owner (or an agent) to a
|
|
// console page goes through here: "App Information > Age Rating: https://appstoreconnect.apple.com/apps/<id>/...",
|
|
// never plain directions alone (pitfall 71). With unknown ids the link falls back to the best page that exists (the
|
|
// app list, the developer chooser), so a message always carries an absolute https URL.
|
|
//
|
|
// Ids: apple.appId, play.developerId and play.appId from the config (loadConfig calls setConsoleIds), or explicit ids.
|
|
// URL shapes reused from console-forms / console-plan (app-dashboard, app-content/*, create-new-app, app-list) are
|
|
// verified; the others follow the consoles' current routes (see UNVERIFIED in the tests' page list).
|
|
|
|
export const ASC = 'https://appstoreconnect.apple.com';
|
|
export const PLAY_CONSOLE = 'https://play.google.com/console';
|
|
|
|
export interface ConsoleIds { appleAppId?: string | null; playDeveloperId?: string | null; playAppId?: string | null }
|
|
|
|
let current: ConsoleIds = {};
|
|
/** Remember the loaded config's ids so every later message deep-links to this app. */
|
|
export function setConsoleIds(ids: ConsoleIds): void { current = { ...ids }; }
|
|
export function consoleIdsFromConfig(c: { apple?: { appId?: string }; play?: { developerId?: string; appId?: string } } | null | undefined): ConsoleIds {
|
|
return { appleAppId: c?.apple?.appId, playDeveloperId: c?.play?.developerId, playAppId: c?.play?.appId };
|
|
}
|
|
|
|
type AppleScope = 'app' | 'account';
|
|
type PlayScope = 'app' | 'developer';
|
|
interface ApplePage { store: 'apple'; label: string; scope: AppleScope; path: string }
|
|
interface PlayPage { store: 'play'; label: string; scope: PlayScope; path: string }
|
|
|
|
/** Every console page storebound points to: readable path + URL path under the app (or account). */
|
|
export const CONSOLE_PAGES = {
|
|
// App Store Connect, per app (/apps/<appId>/...)
|
|
'asc.apps': { store: 'apple', label: 'App Store Connect > Apps', scope: 'account', path: '/apps' },
|
|
'asc.new-app': { store: 'apple', label: 'App Store Connect > Apps > + > New App', scope: 'account', path: '/apps' },
|
|
'asc.app-info': { store: 'apple', label: 'App Store Connect > App Information', scope: 'app', path: '/distribution/info' },
|
|
'asc.age-rating': { store: 'apple', label: 'App Store Connect > App Information > Age Rating', scope: 'app', path: '/distribution/info' },
|
|
'asc.encryption': { store: 'apple', label: 'App Store Connect > App Information > App Encryption Documentation', scope: 'app', path: '/distribution/info' },
|
|
'asc.app-privacy': { store: 'apple', label: 'App Store Connect > App Privacy', scope: 'app', path: '/distribution/privacy' },
|
|
'asc.pricing': { store: 'apple', label: 'App Store Connect > Pricing and Availability', scope: 'app', path: '/distribution/pricing' },
|
|
'asc.version': { store: 'apple', label: 'App Store Connect > the iOS version page', scope: 'app', path: '/distribution/ios/version/inflight' },
|
|
'asc.app-review': { store: 'apple', label: 'App Store Connect > App Review', scope: 'app', path: '/distribution/reviewsubmissions' },
|
|
'asc.testflight': { store: 'apple', label: 'App Store Connect > TestFlight', scope: 'app', path: '/testflight/ios' },
|
|
'asc.iaps': { store: 'apple', label: 'App Store Connect > In-App Purchases', scope: 'app', path: '/distribution/iaps' },
|
|
'asc.subscriptions': { store: 'apple', label: 'App Store Connect > Subscriptions', scope: 'app', path: '/distribution/subscriptions' },
|
|
'asc.ppo': { store: 'apple', label: 'App Store Connect > Product Page Optimization', scope: 'app', path: '/distribution/product-page-optimization' },
|
|
// App Store Connect, account
|
|
'asc.payments': { store: 'apple', label: 'App Store Connect > Payments and Financial Reports', scope: 'account', path: '/itc/payments_and_financial_reports' },
|
|
'asc.users': { store: 'apple', label: 'App Store Connect > Users and Access', scope: 'account', path: '/access/users' },
|
|
'asc.keys': { store: 'apple', label: 'App Store Connect > Users and Access > Integrations > App Store Connect API', scope: 'account', path: '/access/integrations/api' },
|
|
// Play Console, per app (/u/0/developers/<dev>/app/<app>/...)
|
|
'play.dashboard': { store: 'play', label: 'Play Console > Dashboard', scope: 'app', path: 'app-dashboard' },
|
|
'play.app-content': { store: 'play', label: 'Play Console > Policy and programs > App content', scope: 'app', path: 'app-content/overview' },
|
|
'play.data-safety': { store: 'play', label: 'Play Console > App content > Data safety', scope: 'app', path: 'app-content/data-privacy-security' },
|
|
'play.content-rating': { store: 'play', label: 'Play Console > App content > Content ratings', scope: 'app', path: 'app-content/content-rating-overview' },
|
|
'play.target-audience': { store: 'play', label: 'Play Console > App content > Target audience and content', scope: 'app', path: 'app-content/target-audience-content' },
|
|
'play.ads': { store: 'play', label: 'Play Console > App content > Ads', scope: 'app', path: 'app-content/ads-declaration' },
|
|
'play.app-access': { store: 'play', label: 'Play Console > App content > App access', scope: 'app', path: 'app-content/testing-credentials' },
|
|
'play.foreground-service': { store: 'play', label: 'Play Console > App content > Foreground service permissions', scope: 'app', path: 'app-content/foreground-services' },
|
|
'play.sensitive-permissions': { store: 'play', label: 'Play Console > App content > Sensitive app permissions', scope: 'app', path: 'app-content/overview' },
|
|
'play.health-apps': { store: 'play', label: 'Play Console > App content > Health apps', scope: 'app', path: 'app-content/overview' },
|
|
'play.full-screen-intent': { store: 'play', label: 'Play Console > App content > Full-screen intent', scope: 'app', path: 'app-content/overview' },
|
|
'play.store-listing': { store: 'play', label: 'Play Console > Store presence > Main store listing', scope: 'app', path: 'main-store-listing' },
|
|
'play.store-settings': { store: 'play', label: 'Play Console > Store presence > Store settings', scope: 'app', path: 'store-settings' },
|
|
'play.listing-experiments': { store: 'play', label: 'Play Console > Grow users > Store listing experiments', scope: 'app', path: 'store-listing-experiments' },
|
|
'play.releases': { store: 'play', label: 'Play Console > Test and release', scope: 'app', path: 'releases/overview' },
|
|
'play.production': { store: 'play', label: 'Play Console > Test and release > Production', scope: 'app', path: 'tracks/production' },
|
|
'play.internal-testing': { store: 'play', label: 'Play Console > Test and release > Internal testing', scope: 'app', path: 'tracks/internal-testing' },
|
|
'play.publishing': { store: 'play', label: 'Play Console > Publishing overview', scope: 'app', path: 'publishing' },
|
|
'play.app-signing': { store: 'play', label: 'Play Console > Test and release > App integrity > Play app signing', scope: 'app', path: 'keymanagement' },
|
|
'play.bundle-explorer': { store: 'play', label: 'Play Console > Test and release > App bundle explorer', scope: 'app', path: 'bundle-explorer-selector' },
|
|
'play.products': { store: 'play', label: 'Play Console > Monetize with Play > Products > In-app products', scope: 'app', path: 'managed-products' },
|
|
'play.subscriptions': { store: 'play', label: 'Play Console > Monetize with Play > Products > Subscriptions', scope: 'app', path: 'subscriptions' },
|
|
'play.promo-codes': { store: 'play', label: 'Play Console > Monetize with Play > Promo codes', scope: 'app', path: 'promotions' },
|
|
'play.vitals': { store: 'play', label: 'Play Console > Monitor and improve > Android vitals', scope: 'app', path: 'vitals/crashes' },
|
|
'play.policy-status': { store: 'play', label: 'Play Console > Policy and programs > Policy status', scope: 'app', path: 'policy-center/issues' },
|
|
'play.pre-registration': { store: 'play', label: 'Play Console > Test and release > Production > Countries / regions (pre-registration)', scope: 'app', path: 'tracks/production' },
|
|
// Play Console, developer account (/u/0/developers/<dev>/...)
|
|
'play.app-list': { store: 'play', label: 'Play Console > Home (All apps)', scope: 'developer', path: 'app-list' },
|
|
'play.create-app': { store: 'play', label: 'Play Console > Home > Create app', scope: 'developer', path: 'create-new-app' },
|
|
'play.reports-statistics': { store: 'play', label: 'Play Console > Download reports > Statistics', scope: 'developer', path: 'download-reports/statistics' },
|
|
'play.reports-financial': { store: 'play', label: 'Play Console > Download reports > Financial', scope: 'developer', path: 'download-reports/financial' },
|
|
'play.users': { store: 'play', label: 'Play Console > Users and permissions', scope: 'developer', path: 'users-and-permissions' },
|
|
'play.api-access': { store: 'play', label: 'Play Console > Setup > API access', scope: 'developer', path: 'api-access' },
|
|
'play.internal-sharing': { store: 'play', label: 'Play Console > Internal app sharing', scope: 'developer', path: 'internal-app-sharing' },
|
|
} as const satisfies Record<string, ApplePage | PlayPage>;
|
|
|
|
export type ConsolePage = keyof typeof CONSOLE_PAGES;
|
|
|
|
/** The absolute URL of a console page. Unknown ids: the best page that exists (app list / developer chooser). */
|
|
export function consoleUrl(page: ConsolePage, ids: ConsoleIds = current): string {
|
|
const p: ApplePage | PlayPage = CONSOLE_PAGES[page];
|
|
const id = (v: string | null | undefined) => v && /^\d+$/.test(v) ? v : null;
|
|
if (p.store === 'apple') {
|
|
if (p.scope === 'account') return `${ASC}${p.path}`;
|
|
const app = id(ids.appleAppId ?? current.appleAppId);
|
|
return app ? `${ASC}/apps/${app}${p.path}` : `${ASC}/apps`;
|
|
}
|
|
const dev = id(ids.playDeveloperId ?? current.playDeveloperId), app = id(ids.playAppId ?? current.playAppId);
|
|
if (!dev) return PLAY_CONSOLE;
|
|
const base = `${PLAY_CONSOLE}/u/0/developers/${dev}`;
|
|
if (p.scope === 'developer') return `${base}/${p.path}`;
|
|
return app ? `${base}/app/${app}/${p.path}` : `${base}/app-list`;
|
|
}
|
|
|
|
/** True when the URL is the page itself (ids known), false when it is a fallback (app list, chooser). */
|
|
export function consoleUrlExact(page: ConsolePage, ids: ConsoleIds = current): boolean {
|
|
const u = consoleUrl(page, ids);
|
|
return u !== PLAY_CONSOLE && !(CONSOLE_PAGES[page].scope === 'app' && (u === `${ASC}/apps` || u.endsWith('/app-list')));
|
|
}
|
|
|
|
/** "Readable > path: https://..." (the path suffix, e.g. "> Copy Cloud Storage URI", is appended to the label). */
|
|
export function consoleLink(page: ConsolePage, suffix = '', ids: ConsoleIds = current): string {
|
|
return `${CONSOLE_PAGES[page].label}${suffix ? ` ${suffix}` : ''}: ${consoleUrl(page, ids)}`;
|
|
}
|
|
|
|
/** Play track page: production / internal have their own page; other tracks open Test and release. */
|
|
export function playTrackPage(track: string | undefined): ConsolePage {
|
|
return track === 'production' ? 'play.production' : track === 'internal' ? 'play.internal-testing' : 'play.releases';
|
|
}
|