summaryrefslogtreecommitdiff
path: root/src/config
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /src/config
init commit
Diffstat (limited to 'src/config')
-rw-r--r--src/config/build.ts1
-rw-r--r--src/config/components/artwork.ts163
-rw-r--r--src/config/components/shelf.ts208
-rw-r--r--src/config/errorkit.ts17
-rw-r--r--src/config/hlsjs.ts25
-rw-r--r--src/config/media-api/browser.ts1
-rw-r--r--src/config/media-api/search-jwt.ts27
-rw-r--r--src/config/metrics.ts17
-rw-r--r--src/config/rtcjs.ts103
9 files changed, 562 insertions, 0 deletions
diff --git a/src/config/build.ts b/src/config/build.ts
new file mode 100644
index 0000000..d64f14b
--- /dev/null
+++ b/src/config/build.ts
@@ -0,0 +1 @@
+export const BUILD = process.env.VERSION as string;
diff --git a/src/config/components/artwork.ts b/src/config/components/artwork.ts
new file mode 100644
index 0000000..49c0f8e
--- /dev/null
+++ b/src/config/components/artwork.ts
@@ -0,0 +1,163 @@
+import { ASPECT_RATIOS } from '@amp/web-app-components/src/components/Artwork/constants';
+import { ArtworkConfig } from '@amp/web-app-components/config/components/artwork';
+import type { ArtworkProfileMap } from '@amp/web-app-components/config/components/artwork';
+import type { CropCode } from '@amp/web-app-components/src/components/Artwork/types';
+
+const { HD, ONE, THREE_QUARTERS, HD_ASPECT_RATIO } = ASPECT_RATIOS;
+
+const AppIconSize = {
+ DEFAULT: [48],
+ SMALL: [64],
+ MEDIUM: [100],
+ LARGE: [200],
+ XLARGE: [800],
+};
+
+const cardSizes = [525, 525, 490, 394, 738];
+const brickSizes = [520, 400, 450, 450, 300];
+const heroSizes = [1600, 1240, 920, 920, 490];
+
+export type NamedProfile =
+ | 'app-event-detail'
+ | 'app-event-detail-small'
+ | 'app-icon'
+ | 'app-icon-large'
+ | 'app-icon-medium'
+ | 'app-icon-small'
+ | 'app-icon-xlarge'
+ | 'app-icon-pill'
+ | 'app-icon-large-pill'
+ | 'app-icon-medium-pill'
+ | 'app-icon-small-pill'
+ | 'app-icon-river-pill'
+ | 'app-icon-tv-rect'
+ | 'app-icon-large-tv-rect'
+ | 'app-icon-xlarge-tv-rect'
+ | 'app-icon-medium-tv-rect'
+ | 'app-icon-small-tv-rect'
+ | 'app-icon-river-tv-rect'
+ | 'app-icon-river'
+ | 'app-promotion'
+ | 'app-promotion-in-article'
+ | 'app-trailer-lockup-video'
+ | 'brick'
+ | 'brick-app-icon'
+ | 'card'
+ | 'card-horizontal'
+ | 'category-brick'
+ | 'editorial-story-card'
+ | 'in-app-purchase'
+ | 'large-brick'
+ | 'large-hero'
+ | 'large-hero-portrait'
+ | 'large-hero-portrait-iphone'
+ | 'large-hero-breakout'
+ | 'large-hero-breakout-rtl'
+ | 'large-hero-west'
+ | 'large-hero-east'
+ | 'large-hero-story-card'
+ | 'large-hero-story-card-portrait'
+ | 'large-hero-story-card-rtl'
+ | 'large-image-lockup'
+ | 'poster-lockup'
+ | 'poster-title'
+ | 'medium-story-card'
+ | 'screenshot-vision'
+ | 'screenshot-phone'
+ | 'screenshot-phone_portrait'
+ | 'screenshot-iphone_5_8'
+ | 'screenshot-iphone_5_8_portrait'
+ | 'screenshot-iphone_6_5'
+ | 'screenshot-iphone_6_5_portrait'
+ | 'screenshot-iphone_d74'
+ | 'screenshot-iphone_d74_portrait'
+ | 'screenshot-mac'
+ | 'screenshot-tv'
+ | 'screenshot-pad'
+ | 'screenshot-pad-portrait'
+ | 'screenshot-watch'
+ | 'small-brick'
+ | 'small-story-card-portrait'
+ | 'small-story-card'
+ | 'small-story-card-legacy'
+ | 'uber-shelf';
+
+const PROFILES: ArtworkProfileMap<NamedProfile> = new Map([
+ ['app-event-detail', [[480, 336, 336], 9 / 16, 'sr']],
+ ['app-event-detail-small', [[480, 336, 336], HD_ASPECT_RATIO, 'sr']],
+ ['app-icon', [AppIconSize.DEFAULT, ONE, 'bb']],
+ ['app-icon-large', [AppIconSize.LARGE, ONE, 'bb']],
+ ['app-icon-medium', [AppIconSize.MEDIUM, ONE, 'bb']],
+ ['app-icon-small', [AppIconSize.SMALL, ONE, 'bb']],
+ ['app-icon-xlarge', [AppIconSize.XLARGE, ONE, 'bb']],
+ ['app-icon-pill', [AppIconSize.DEFAULT, 4 / 3, 'sr']],
+ ['app-icon-large-pill', [AppIconSize.LARGE, 4 / 3, 'sr']],
+ ['app-icon-medium-pill', [AppIconSize.MEDIUM, 4 / 3, 'sr']],
+ ['app-icon-small-pill', [AppIconSize.SMALL, 4 / 3, 'sr']],
+ ['app-icon-tv-rect', [AppIconSize.DEFAULT, HD, 'sr']],
+ ['app-icon-large-tv-rect', [AppIconSize.LARGE, HD, 'sr']],
+ ['app-icon-xlarge-tv-rect', [AppIconSize.XLARGE, HD, 'sr']],
+ ['app-icon-medium-tv-rect', [AppIconSize.MEDIUM, HD, 'sr']],
+ ['app-icon-small-tv-rect', [AppIconSize.SMALL, HD, 'bb']],
+ ['app-icon-river-tv-rect', [[128, 128, 128, 88, 88], HD, 'bb']],
+ ['app-icon-river', [[128, 128, 128, 88, 88], ONE, 'bb']],
+ ['app-icon-river-pill', [[128, 128, 128, 88, 88], 4 / 3, 'sr']],
+ ['app-promotion', [[385, 330, 400, 450, 298], 16 / 9, 'sr']],
+ ['app-promotion-in-article', [[800, 600], 16 / 9, 'sr']],
+ ['app-trailer-lockup-video', [[385, 330, 400, 450, 298], 16 / 10, 'sr']],
+ ['brick', [brickSizes, HD, 'sr']],
+ ['brick-app-icon', [[83, 60, 60, 60, 50], ONE, 'bb']],
+ ['card', [cardSizes, THREE_QUARTERS, 'sr']],
+ ['card-horizontal', [[1020], HD, 'sr']],
+ ['category-brick', [[368, 368, 368, 208, 288], HD, 'sr']],
+ ['editorial-story-card', [[385, 400, 450], THREE_QUARTERS, 'sr']],
+ ['in-app-purchase', [[153, 160, 160, 140, 168], ONE, 'sr']],
+ ['large-brick', [[520, 610, 450, 298, 298], HD, 'sr']],
+ ['large-hero', [heroSizes, HD, 'sr']],
+ ['large-hero-portrait', [[430], 9 / 16, 'sr']],
+ ['large-hero-portrait-iphone', [[430], THREE_QUARTERS, 'SH.ApCSC01']],
+ ['large-hero-west', [heroSizes, 2.79, 'grav.west']],
+ ['large-hero-east', [heroSizes, 2.79, 'grav.east']],
+ ['large-hero-story-card', [heroSizes, 2.25, 'CC.ApSHW01']],
+ ['large-hero-story-card-rtl', [heroSizes, 2.25, 'sr']],
+ ['large-hero-story-card-portrait', [cardSizes, 3 / 4, 'CC.ApSHT01']],
+ ['large-hero-breakout', [heroSizes, 8 / 3, 'sr']],
+ ['large-image-lockup', [[790, 610, 919, 298, 298], HD, 'sr']],
+ ['poster-lockup', [[520, 520, 400, 450, 300], HD, 'sr']],
+ ['poster-title', [[400, 300, 200], HD, 'bb']],
+ [
+ 'large-hero-breakout-rtl',
+ [[1600, 1240, 920, 920, 688], 8 / 3, 'gk' as CropCode],
+ ], // the `rtl` version of `large-hero-breakout` assets max out at 1344px wide
+ ['medium-story-card', [[298, 579, 490, 394], THREE_QUARTERS, 'sr']],
+ ['small-brick', [[300, 300, 300, 200, 300], HD, 'sr']],
+ ['small-story-card-portrait', [[182, 232, 215, 192], THREE_QUARTERS, 'sr']],
+ ['screenshot-vision', [[480, 480, 335, 520, 520], HD, 'sr']],
+ ['screenshot-phone', [[313, 643, 313, 480, 643], 20 / 9, 'w']],
+ ['screenshot-phone_portrait', [[230, 230, 157, 300, 300], 9 / 20, 'w']],
+ ['screenshot-iphone_5_8', [[313, 643, 313, 480, 643], 498 / 230, 'w']],
+ [
+ 'screenshot-iphone_5_8_portrait',
+ [[230, 230, 157, 300, 300], 230 / 498, 'w'],
+ ],
+ ['screenshot-iphone_6_5', [[313, 643, 313, 480, 643], 498 / 230, 'w']],
+ [
+ 'screenshot-iphone_6_5_portrait',
+ [[230, 230, 157, 300, 300], 230 / 498, 'w'],
+ ],
+ ['screenshot-iphone_d74', [[313, 643, 313, 480, 643], 466 / 215, 'w']],
+ [
+ 'screenshot-iphone_d74_portrait',
+ [[230, 230, 157, 300, 300], 215 / 466, 'w'],
+ ],
+ ['screenshot-mac', [[313, 643, 313, 480, 643], 16 / 10, 'w']],
+ ['screenshot-tv', [[313, 643, 313, 480, 643], HD, 'w']],
+ ['screenshot-pad', [[313, 643, 313, 480, 643], 4 / 3, 'w']],
+ ['screenshot-pad-portrait', [[480, 528, 313, 480, 643], 3 / 4, 'w']],
+ ['screenshot-watch', [[300, 157, 230, 230, 230], 4 / 5, 'w']],
+ ['small-story-card', [brickSizes, HD, 'CC.ApSHSC01']],
+ ['small-story-card-legacy', [brickSizes, HD, 'SCS.ApDPCS01']],
+ ['uber-shelf', [[1680, 1680, 1320, 1000, 390], 8 / 3, 'sr']],
+]);
+
+ArtworkConfig.set({ PROFILES });
diff --git a/src/config/components/shelf.ts b/src/config/components/shelf.ts
new file mode 100644
index 0000000..515bf0f
--- /dev/null
+++ b/src/config/components/shelf.ts
@@ -0,0 +1,208 @@
+import { ShelfConfig } from '@amp/web-app-components/config/components/shelf';
+
+const defaultShelfConfig = ShelfConfig.get();
+const { GRID_MAX_CONTENT, GRID_VALUES, GRID_ROW_GAP } = defaultShelfConfig;
+
+ShelfConfig.set({
+ ...defaultShelfConfig,
+ GRID_MAX_CONTENT: {
+ ...GRID_MAX_CONTENT,
+ Brick: GRID_MAX_CONTENT.A,
+ FooterLink: GRID_MAX_CONTENT.A,
+ InAppPurchaseLockup: GRID_MAX_CONTENT.C,
+ LargeBrick: GRID_MAX_CONTENT.A,
+ LargeLockup: GRID_MAX_CONTENT.C,
+ MediumLockup: GRID_MAX_CONTENT.A,
+ PosterLockup: GRID_MAX_CONTENT.A,
+ ScreenshotLarge: GRID_MAX_CONTENT.A,
+ ScreenshotVision: GRID_MAX_CONTENT.A,
+ ScreenshotPhone: GRID_MAX_CONTENT.G,
+ ScreenshotPad: GRID_MAX_CONTENT.A,
+ SearchLink: GRID_MAX_CONTENT.A,
+ SearchResult: GRID_MAX_CONTENT.A,
+ SmallLockup: GRID_MAX_CONTENT.A,
+ SmallLockupWithOrdinal: {},
+ SmallStoryCard: GRID_MAX_CONTENT.A,
+ ProductBadge: GRID_MAX_CONTENT.D,
+ },
+ GRID_VALUES: {
+ ...GRID_VALUES,
+ Brick: {
+ ...GRID_VALUES.A,
+ medium: 3,
+ },
+ InAppPurchaseLockup: {
+ xsmall: 3,
+ small: 5,
+ medium: 6,
+ large: 8,
+ xlarge: 8,
+ },
+ LargeBrick: {
+ ...GRID_VALUES.C,
+ small: 2,
+ medium: 2,
+ large: 3,
+ xlarge: 3,
+ },
+ LargeLockup: {
+ xsmall: 2,
+ small: 3,
+ medium: 4,
+ large: 5,
+ xlarge: 6,
+ },
+ MediumLockup: {
+ xsmall: 2,
+ small: 2,
+ medium: 4,
+ large: 5,
+ xlarge: 5,
+ },
+ PosterLockup: {
+ ...GRID_VALUES.A,
+ xsmall: 1,
+ large: 2,
+ },
+ ProductBadge: {
+ ...GRID_VALUES.D,
+ small: 5,
+ medium: 6,
+ },
+ SearchLink: {
+ xsmall: 1,
+ small: 2,
+ medium: 3,
+ large: 3,
+ xlarge: 3,
+ },
+ SearchResult: {
+ xsmall: 1,
+ small: 2,
+ medium: 3,
+ large: 3,
+ xlarge: 3,
+ },
+ FooterLink: {
+ xsmall: 1,
+ small: 2,
+ medium: 3,
+ large: 3,
+ xlarge: 3,
+ },
+ SmallLockup: {
+ xsmall: 2,
+ small: 2,
+ medium: 3,
+ large: 4,
+ xlarge: 4,
+ },
+ SmallLockupWithOrdinal: {
+ xsmall: 2,
+ small: 4,
+ medium: 5,
+ large: 6,
+ xlarge: 6,
+ },
+ SmallStoryCard: {
+ xsmall: 2,
+ small: 2,
+ medium: 2,
+ large: 2,
+ xlarge: 2,
+ },
+ ScreenshotLarge: {
+ xsmall: 1,
+ small: 2,
+ medium: 2,
+ large: 3,
+ xlarge: 3,
+ },
+ ScreenshotVision: {
+ xsmall: 1,
+ small: 1,
+ medium: 2,
+ large: 3,
+ xlarge: 3,
+ },
+ ScreenshotPhone: {
+ xsmall: 2,
+ small: 3,
+ medium: 4,
+ large: 5,
+ xlarge: 5,
+ },
+ ScreenshotPad: {
+ xsmall: 1,
+ small: 3,
+ medium: 4,
+ large: 4,
+ xlarge: 4,
+ },
+ },
+ GRID_ROW_GAP: {
+ ...GRID_ROW_GAP,
+ Brick: GRID_ROW_GAP.None,
+ FooterLink: GRID_ROW_GAP.None,
+ InAppPurchaseLockup: GRID_ROW_GAP.None,
+ LargeBrick: {
+ xsmall: 24,
+ small: 24,
+ medium: 24,
+ large: 24,
+ xlarge: 24,
+ },
+ LargeLockup: {
+ xsmall: 20,
+ small: 20,
+ medium: 20,
+ large: 20,
+ xlarge: 20,
+ },
+ MediumLockup: {
+ xsmall: 24,
+ small: 24,
+ medium: 24,
+ large: 24,
+ xlarge: 24,
+ },
+ PosterLockup: GRID_ROW_GAP.None,
+ ScreenshotLarge: GRID_ROW_GAP.None,
+ ScreenshotVision: GRID_ROW_GAP.None,
+ ScreenshotPhone: GRID_ROW_GAP.None,
+ ScreenshotPad: GRID_ROW_GAP.None,
+ SearchLink: {
+ xsmall: 10,
+ small: 20,
+ medium: 20,
+ large: 20,
+ xlarge: 20,
+ },
+ SearchResult: {
+ xsmall: 24,
+ small: 24,
+ medium: 24,
+ large: 24,
+ xlarge: 24,
+ },
+ SmallLockup: {
+ xsmall: 24,
+ small: 24,
+ medium: 24,
+ large: 24,
+ xlarge: 24,
+ },
+ SmallLockupWithOrdinal: {
+ xsmall: 24,
+ small: 24,
+ medium: 24,
+ large: 24,
+ xlarge: 24,
+ },
+ SmallStoryCard: GRID_ROW_GAP.None,
+ ProductBadge: GRID_ROW_GAP.None,
+ },
+ GRID_COL_GAP: {
+ ProductBadge: { small: '20', medium: '0', large: '0', xlarge: '0' },
+ },
+});
diff --git a/src/config/errorkit.ts b/src/config/errorkit.ts
new file mode 100644
index 0000000..4178680
--- /dev/null
+++ b/src/config/errorkit.ts
@@ -0,0 +1,17 @@
+import { BUILD } from './build';
+import type { ErrorKitConfig } from '@amp/web-apps-logger/src/errorkit';
+
+const APPS_PROD_SUBDOMAIN = ['apps'];
+const PROJECT_ID = 'onyx_apps';
+
+const getSentryEnv = () => {
+ const location =
+ typeof window !== 'undefined' && window.location.host.split('.')[0];
+ return APPS_PROD_SUBDOMAIN.includes(location) ? 'prod' : 'dev';
+};
+
+export const ERROR_KIT_CONFIG: ErrorKitConfig = {
+ project: PROJECT_ID,
+ environment: getSentryEnv(),
+ release: BUILD,
+};
diff --git a/src/config/hlsjs.ts b/src/config/hlsjs.ts
new file mode 100644
index 0000000..0551d94
--- /dev/null
+++ b/src/config/hlsjs.ts
@@ -0,0 +1,25 @@
+declare global {
+ interface Window {
+ Hls?: any;
+ }
+}
+
+/**
+ * Base URL for CDN hosting HLS.js files
+ */
+export const HLSJS_CDN = 'https://js-cdn.music.apple.com/hls.js';
+
+/**
+ * HLS.js version to load.
+ */
+export const HLSJS_VERSION = '2.820.0';
+
+/**
+ * Generate a URL for loading HLS.js.
+ */
+export function generateHLSJSURL(version?: string): URL {
+ // FIXME: Add a local storage override for the HLS.js version
+ version = version ?? HLSJS_VERSION;
+
+ return new URL(`${HLSJS_CDN}/${version}/hls.js/hls.js`);
+}
diff --git a/src/config/media-api/browser.ts b/src/config/media-api/browser.ts
new file mode 100644
index 0000000..91cf7c2
--- /dev/null
+++ b/src/config/media-api/browser.ts
@@ -0,0 +1 @@
+export const MEDIA_API_JWT = import.meta.env.BROWSER_MEDIA_API_JWT ?? '';
diff --git a/src/config/media-api/search-jwt.ts b/src/config/media-api/search-jwt.ts
new file mode 100644
index 0000000..1f7e3d2
--- /dev/null
+++ b/src/config/media-api/search-jwt.ts
@@ -0,0 +1,27 @@
+export function shouldUseSearchJWT(url: URL): boolean {
+ // We should only ever use the "search" JWT on the server
+ if (!import.meta.env.SSR) {
+ return false;
+ }
+
+ // Search API Endpoint
+ if (url.pathname.endsWith('/search')) {
+ return true;
+ }
+
+ // All other endpoints should use the default JWT
+ return false;
+}
+
+/**
+ * Creates the `Authorization` header using the App Store "search JWT"
+ *
+ * Note: this function specifically returns a bad value for a "browser"
+ * build so that the "search JWT" is removed from the browser payload
+ * by dead-code elimination
+ */
+export function makeSearchJWTAuthorizationHeader() {
+ return import.meta.env.SSR
+ ? { Authorization: `Bearer ${import.meta.env.SEARCH_MEDIA_API_JWT}` }
+ : { Authorization: '' };
+}
diff --git a/src/config/metrics.ts b/src/config/metrics.ts
new file mode 100644
index 0000000..b9ff0b4
--- /dev/null
+++ b/src/config/metrics.ts
@@ -0,0 +1,17 @@
+import { BUILD } from './build';
+
+const APP_NAME = 'com.apple.apps';
+const APP_DELEGATE = 'web-appstore-app';
+
+export const config = {
+ baseFields: {
+ appName: APP_NAME,
+ delegateApp: APP_DELEGATE,
+ appVersion: BUILD,
+ resourceRevNum: BUILD,
+ },
+ clickstream: {
+ constraintProfiles: ['AMPWeb'],
+ topic: 'xp_amp_appstore_unidentified',
+ },
+};
diff --git a/src/config/rtcjs.ts b/src/config/rtcjs.ts
new file mode 100644
index 0000000..f9e8a67
--- /dev/null
+++ b/src/config/rtcjs.ts
@@ -0,0 +1,103 @@
+import { platform } from '@amp/web-apps-utils';
+import { HLSJS_CDN, HLSJS_VERSION } from './hlsjs';
+
+declare global {
+ interface Window {
+ rtc?: any;
+ }
+}
+
+export type ReportingOptions = {
+ storeBagURL: string;
+ clientName: string;
+ serviceName: string;
+ applicationName: string;
+ applicationVersion: string;
+ browserName: string;
+ browserMajorVersion: string;
+ browserMinorVersion: string;
+ osName: string;
+ osVersion: string;
+};
+
+/**
+ * Generate a URL for loading HLS.js.
+ */
+export function generateRTCJSURL(version?: string): URL {
+ // FIXME: Add a local storage override for the HLS.js version
+ version = version ?? HLSJS_VERSION;
+
+ return new URL(`${HLSJS_CDN}/${version}/rtc.js/rtc.js`);
+}
+
+export function getRTCNamespace() {
+ if (window.rtc === undefined) {
+ throw new Error('Unable to load RTC library');
+ }
+
+ return window.rtc;
+}
+
+export function getReportingOptions(): ReportingOptions {
+ // FIXME: Add correct information for RTC reporting for Web App Store
+ return {
+ storeBagURL:
+ 'https://mediaservices.cdn-apple.com/store_bags/hlsjs/aasw/v1/rtc_storebag.json',
+
+ // Application
+ clientName: 'AASW',
+ serviceName: 'com.apple.apps.external',
+ applicationName: 'AppleAppStoreVWeb',
+ applicationVersion: 'WebAppStore/1.0.0',
+
+ // Browser
+ browserName: platform.clientName() ?? '',
+ browserMajorVersion: platform.majorVersion()?.toString() ?? '0',
+ browserMinorVersion: platform.minorVersion()?.toString() ?? '0',
+
+ // Operating System
+ osName: platform.osName() ?? '',
+ osVersion: platform.osName() ?? '',
+ } as const;
+}
+
+/**
+ * Generate the configuration used for an `RTCReportingAgent`.
+ *
+ * @see {@link makeReportingAgent}
+ */
+export function generateReportingConfig(rtc: any) {
+ rtc = rtc ?? getRTCNamespace();
+ const options = getReportingOptions();
+ const key = rtc.RTCReportingAgentConfigKeys;
+
+ return {
+ [key.Sender]: 'HLSJS',
+ [key.ClientName]: options.clientName,
+ [key.ServiceName]: options.serviceName,
+ [key.ApplicationName]: options.applicationName,
+ [key.DeviceName]: options.osVersion,
+ [key.ReportingStoreBag]: new rtc.RTCStorebag.RTCReportingStoreBag(
+ options.storeBagURL,
+ options.clientName,
+ options.serviceName,
+ options.applicationName,
+ options.browserName,
+ { iTunesAppVersion: options.applicationVersion },
+ ),
+
+ // Fake out these fields
+ model: options.browserName,
+ firmwareVersion: `${options.browserMajorVersion}.${options.browserMinorVersion}`,
+ };
+}
+
+/**
+ * Create an `RTCReportingAgent` with default configuration from `generateReportingConfig`.
+ *
+ * The reporting agent can be used with HLS.js playback to enable RTC reporting.
+ */
+export function makeReportingAgent(rtc: any): any {
+ rtc = rtc ?? getRTCNamespace();
+ return new rtc.RTCReportingAgent(generateReportingConfig(rtc));
+}