diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /src/config | |
init commit
Diffstat (limited to 'src/config')
| -rw-r--r-- | src/config/build.ts | 1 | ||||
| -rw-r--r-- | src/config/components/artwork.ts | 163 | ||||
| -rw-r--r-- | src/config/components/shelf.ts | 208 | ||||
| -rw-r--r-- | src/config/errorkit.ts | 17 | ||||
| -rw-r--r-- | src/config/hlsjs.ts | 25 | ||||
| -rw-r--r-- | src/config/media-api/browser.ts | 1 | ||||
| -rw-r--r-- | src/config/media-api/search-jwt.ts | 27 | ||||
| -rw-r--r-- | src/config/metrics.ts | 17 | ||||
| -rw-r--r-- | src/config/rtcjs.ts | 103 |
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)); +} |
